@toolbox-web/grid-angular 0.12.1 → 0.13.1

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.
@@ -381,9 +381,20 @@ class GridFormArray {
381
381
  // Subscribe to valueChanges to sync grid rows when FormArray content changes.
382
382
  // Use startWith to immediately sync the current value.
383
383
  // Note: We use getRawValue() to include disabled controls.
384
+ //
385
+ // In grid mode, editors bind directly to FormControls, so every keystroke
386
+ // fires valueChanges. We skip the sync when an editor input is focused to
387
+ // prevent destroying/recreating editors mid-edit (which orphans overlays
388
+ // like mat-autocomplete/mat-select panels and causes focus loss).
384
389
  this.valueChangesSubscription = formArray.valueChanges
385
390
  .pipe(startWith(formArray.getRawValue()), takeUntilDestroyed(this.destroyRef))
386
391
  .subscribe(() => {
392
+ // Skip sync while an editor is actively focused in the grid.
393
+ // The FormArray already has the latest values via its own controls;
394
+ // re-setting grid.rows would create new object references and trigger
395
+ // an unnecessary render cycle.
396
+ if (this.#isEditorFocused(grid))
397
+ return;
387
398
  grid.rows = formArray.getRawValue();
388
399
  });
389
400
  }, ...(ngDevMode ? [{ debugName: "syncFormArrayToGrid" }] : []));
@@ -458,6 +469,20 @@ class GridFormArray {
458
469
  const editingPlugin = grid.getPluginByName?.('editing');
459
470
  return editingPlugin?.config?.mode === 'grid';
460
471
  }
472
+ /**
473
+ * Checks if a focusable editor element inside the grid currently has focus.
474
+ * Used to skip valueChanges → grid.rows sync while a user is actively editing,
475
+ * preventing editor destruction (which orphans overlay panels like autocomplete/select).
476
+ */
477
+ #isEditorFocused(grid) {
478
+ if (!this.#isGridMode())
479
+ return false;
480
+ const active = document.activeElement;
481
+ if (!active)
482
+ return false;
483
+ // Check if the focused element is inside the grid
484
+ return grid.contains(active) && active.closest('.editing') != null;
485
+ }
461
486
  /**
462
487
  * Sets up reactive validation syncing for grid mode.
463
488
  * Subscribes to statusChanges on all FormControls to update validation state in real-time.
@@ -552,6 +577,8 @@ class GridFormArray {
552
577
  */
553
578
  #storeFormContext(grid) {
554
579
  const getRowFormGroup = (rowIndex) => this.#getRowFormGroup(rowIndex);
580
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
581
+ const self = this;
555
582
  const context = {
556
583
  getRow: (rowIndex) => {
557
584
  const formArray = this.formArray();
@@ -571,7 +598,9 @@ class GridFormArray {
571
598
  getValue: () => {
572
599
  return this.formArray().getRawValue();
573
600
  },
574
- hasFormGroups: this.#isFormArrayOfFormGroups(),
601
+ get hasFormGroups() {
602
+ return self.#isFormArrayOfFormGroups();
603
+ },
575
604
  getControl: (rowIndex, field) => {
576
605
  const rowFormGroup = getRowFormGroup(rowIndex);
577
606
  if (!rowFormGroup)
@@ -1340,6 +1369,10 @@ class GridAdapter {
1340
1369
  viewContainerRef;
1341
1370
  viewRefs = [];
1342
1371
  componentRefs = [];
1372
+ /** Editor-specific view refs tracked separately for per-cell cleanup via releaseCell. */
1373
+ editorViewRefs = [];
1374
+ /** Editor-specific component refs tracked separately for per-cell cleanup via releaseCell. */
1375
+ editorComponentRefs = [];
1343
1376
  typeRegistry = null;
1344
1377
  constructor(injector, appRef, viewContainerRef) {
1345
1378
  this.injector = injector;
@@ -1498,8 +1531,24 @@ class GridAdapter {
1498
1531
  this.viewRefs.push(viewRef);
1499
1532
  // Trigger change detection
1500
1533
  viewRef.detectChanges();
1501
- // Get the first root node (the component's host element)
1502
- const rootNode = viewRef.rootNodes[0];
1534
+ // Find the first Element root node. When *tbwRenderer is used on <ng-container>,
1535
+ // rootNodes[0] is a comment node (<!--ng-container-->); the actual content is in
1536
+ // subsequent root nodes. For single-element templates, rootNodes[0] IS the element.
1537
+ let rootNode = viewRef.rootNodes[0];
1538
+ const elementNodes = viewRef.rootNodes.filter((n) => n.nodeType === Node.ELEMENT_NODE);
1539
+ if (elementNodes.length === 1) {
1540
+ // Single element among the root nodes — use it directly
1541
+ rootNode = elementNodes[0];
1542
+ }
1543
+ else if (elementNodes.length > 1) {
1544
+ // Multiple element nodes — wrap in a span container so all are rendered
1545
+ const wrapper = document.createElement('span');
1546
+ wrapper.style.display = 'contents';
1547
+ for (const node of viewRef.rootNodes) {
1548
+ wrapper.appendChild(node);
1549
+ }
1550
+ rootNode = wrapper;
1551
+ }
1503
1552
  // Cache for reuse on scroll recycles
1504
1553
  if (cellEl) {
1505
1554
  cellCache.set(cellEl, { viewRef, rootNode });
@@ -1580,7 +1629,8 @@ class GridAdapter {
1580
1629
  };
1581
1630
  // Create embedded view from template
1582
1631
  const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
1583
- this.viewRefs.push(viewRef);
1632
+ // Track in editor-specific array for per-cell cleanup via releaseCell
1633
+ this.editorViewRefs.push(viewRef);
1584
1634
  // Trigger change detection
1585
1635
  viewRef.detectChanges();
1586
1636
  // Get the first root node (the component's host element)
@@ -1798,7 +1848,7 @@ class GridAdapter {
1798
1848
  * Shared logic between renderer and editor component creation.
1799
1849
  * @internal
1800
1850
  */
1801
- mountComponent(componentClass, inputs) {
1851
+ mountComponent(componentClass, inputs, isEditor = false) {
1802
1852
  // Create a host element for the component
1803
1853
  const hostElement = document.createElement('span');
1804
1854
  hostElement.style.display = 'contents';
@@ -1811,7 +1861,13 @@ class GridAdapter {
1811
1861
  this.setComponentInputs(componentRef, inputs);
1812
1862
  // Attach to app for change detection
1813
1863
  this.appRef.attachView(componentRef.hostView);
1814
- this.componentRefs.push(componentRef);
1864
+ // Track in editor-specific array for per-cell cleanup, or general array for renderers
1865
+ if (isEditor) {
1866
+ this.editorComponentRefs.push(componentRef);
1867
+ }
1868
+ else {
1869
+ this.componentRefs.push(componentRef);
1870
+ }
1815
1871
  // Trigger change detection
1816
1872
  componentRef.changeDetectorRef.detectChanges();
1817
1873
  return { hostElement, componentRef };
@@ -1878,7 +1934,7 @@ class GridAdapter {
1878
1934
  value: ctx.value,
1879
1935
  row: ctx.row,
1880
1936
  column: ctx.column,
1881
- });
1937
+ }, true);
1882
1938
  this.wireEditorCallbacks(hostElement, componentRef, (value) => ctx.commit(value), () => ctx.cancel());
1883
1939
  // Auto-update editor when value changes externally (e.g., via updateRow cascade).
1884
1940
  // This keeps Angular component editors in sync without manual DOM patching.
@@ -1959,6 +2015,33 @@ class GridAdapter {
1959
2015
  }
1960
2016
  }
1961
2017
  }
2018
+ /**
2019
+ * Called when a cell's content is about to be wiped (e.g., exiting edit mode,
2020
+ * scroll-recycling a row, or rebuilding a row).
2021
+ *
2022
+ * Destroys any editor embedded views or component refs whose DOM is
2023
+ * inside the given cell element. This prevents memory leaks from
2024
+ * orphaned Angular views that would otherwise stay in the change
2025
+ * detection tree indefinitely.
2026
+ */
2027
+ releaseCell(cellEl) {
2028
+ // Release editor embedded views whose root nodes are inside this cell
2029
+ for (let i = this.editorViewRefs.length - 1; i >= 0; i--) {
2030
+ const ref = this.editorViewRefs[i];
2031
+ if (ref.rootNodes.some((n) => cellEl.contains(n))) {
2032
+ ref.destroy();
2033
+ this.editorViewRefs.splice(i, 1);
2034
+ }
2035
+ }
2036
+ // Release editor component refs whose host element is inside this cell
2037
+ for (let i = this.editorComponentRefs.length - 1; i >= 0; i--) {
2038
+ const ref = this.editorComponentRefs[i];
2039
+ if (cellEl.contains(ref.location.nativeElement)) {
2040
+ ref.destroy();
2041
+ this.editorComponentRefs.splice(i, 1);
2042
+ }
2043
+ }
2044
+ }
1962
2045
  /**
1963
2046
  * Clean up all view references and component references.
1964
2047
  * Call this when your app/component is destroyed.
@@ -1966,8 +2049,12 @@ class GridAdapter {
1966
2049
  destroy() {
1967
2050
  this.viewRefs.forEach((ref) => ref.destroy());
1968
2051
  this.viewRefs = [];
2052
+ this.editorViewRefs.forEach((ref) => ref.destroy());
2053
+ this.editorViewRefs = [];
1969
2054
  this.componentRefs.forEach((ref) => ref.destroy());
1970
2055
  this.componentRefs = [];
2056
+ this.editorComponentRefs.forEach((ref) => ref.destroy());
2057
+ this.editorComponentRefs = [];
1971
2058
  }
1972
2059
  }
1973
2060
  /**
@@ -2429,6 +2516,94 @@ function clearFeatureRegistry() {
2429
2516
  warnedFeatures.clear();
2430
2517
  }
2431
2518
 
2519
+ /**
2520
+ * Base class for Angular filter panel components.
2521
+ *
2522
+ * Provides a ready-made `params` input and common lifecycle helpers
2523
+ * (`applyAndClose`, `clearAndClose`) so consumers only need to implement
2524
+ * their filter logic in `applyFilter()`.
2525
+ *
2526
+ * ## Usage
2527
+ *
2528
+ * ```typescript
2529
+ * import { Component } from '@angular/core';
2530
+ * import { BaseFilterPanel } from '@toolbox-web/grid-angular';
2531
+ *
2532
+ * @Component({
2533
+ * selector: 'app-text-filter',
2534
+ * template: `
2535
+ * <input #input (keydown.enter)="applyAndClose()" />
2536
+ * <button (click)="applyAndClose()">Apply</button>
2537
+ * <button (click)="clearAndClose()">Clear</button>
2538
+ * `
2539
+ * })
2540
+ * export class TextFilterComponent extends BaseFilterPanel {
2541
+ * @ViewChild('input') input!: ElementRef<HTMLInputElement>;
2542
+ *
2543
+ * applyFilter(): void {
2544
+ * this.params().applyTextFilter('contains', this.input.nativeElement.value);
2545
+ * }
2546
+ * }
2547
+ * ```
2548
+ *
2549
+ * ## Template Syntax
2550
+ *
2551
+ * The grid's filtering plugin will mount this component and provide `params`
2552
+ * automatically. No manual wiring is required:
2553
+ *
2554
+ * ```typescript
2555
+ * gridConfig = {
2556
+ * columns: [
2557
+ * { field: 'name', filterable: true, filterPanel: TextFilterComponent },
2558
+ * ],
2559
+ * };
2560
+ * ```
2561
+ *
2562
+ * @typeParam TRow - The row data type (available via `params().column`)
2563
+ */
2564
+ class BaseFilterPanel {
2565
+ /**
2566
+ * Filter panel parameters injected by the grid's filtering plugin.
2567
+ *
2568
+ * Provides access to:
2569
+ * - `field` — the column field name
2570
+ * - `column` — full column configuration
2571
+ * - `uniqueValues` — distinct values in the column
2572
+ * - `excludedValues` — currently excluded values (set filter)
2573
+ * - `searchText` — current search text
2574
+ * - `applySetFilter(excluded)` — apply a set-based (include/exclude) filter
2575
+ * - `applyTextFilter(operator, value, valueTo?)` — apply a text/number filter
2576
+ * - `clearFilter()` — clear the filter for this column
2577
+ * - `closePanel()` — close the filter panel
2578
+ */
2579
+ params = input.required(...(ngDevMode ? [{ debugName: "params" }] : []));
2580
+ /**
2581
+ * Apply the filter then close the panel.
2582
+ *
2583
+ * Calls {@link applyFilter} followed by `params().closePanel()`.
2584
+ * Bind this to your "Apply" button or Enter key handler.
2585
+ */
2586
+ applyAndClose() {
2587
+ this.applyFilter();
2588
+ this.params().closePanel();
2589
+ }
2590
+ /**
2591
+ * Clear the filter then close the panel.
2592
+ *
2593
+ * Calls `params().clearFilter()` followed by `params().closePanel()`.
2594
+ * Bind this to your "Clear" / "Reset" button.
2595
+ */
2596
+ clearAndClose() {
2597
+ this.params().clearFilter();
2598
+ this.params().closePanel();
2599
+ }
2600
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BaseFilterPanel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2601
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.1", type: BaseFilterPanel, isStandalone: true, inputs: { params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
2602
+ }
2603
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BaseFilterPanel, decorators: [{
2604
+ type: Directive
2605
+ }], propDecorators: { params: [{ type: i0.Input, args: [{ isSignal: true, alias: "params", required: true }] }] } });
2606
+
2432
2607
  /**
2433
2608
  * Base class for grid cell editors.
2434
2609
  *
@@ -2699,6 +2874,712 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
2699
2874
  type: Directive
2700
2875
  }], ctorParameters: () => [], 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"] }] } });
2701
2876
 
2877
+ /**
2878
+ * Base class for grid editors that also work as Angular form controls.
2879
+ *
2880
+ * Combines `BaseGridEditor` with `ControlValueAccessor` so the same component
2881
+ * can be used inside a `<tbw-grid>` **and** in a standalone `<form>`.
2882
+ *
2883
+ * ## What it provides
2884
+ *
2885
+ * | Member | Purpose |
2886
+ * |--------|---------|
2887
+ * | `cvaValue` | Signal holding the value written by the form control |
2888
+ * | `disabledState` | Signal tracking `setDisabledState` calls |
2889
+ * | `displayValue` | Computed that prefers grid value (`currentValue`) and falls back to `cvaValue` |
2890
+ * | `commitBoth(v)` | Commits via both CVA `onChange` and grid `commitValue` |
2891
+ * | `writeValue` / `registerOn*` / `setDisabledState` | Full CVA implementation |
2892
+ *
2893
+ * ## Usage
2894
+ *
2895
+ * ```typescript
2896
+ * import { Component, forwardRef } from '@angular/core';
2897
+ * import { NG_VALUE_ACCESSOR } from '@angular/forms';
2898
+ * import { BaseGridEditorCVA } from '@toolbox-web/grid-angular';
2899
+ *
2900
+ * @Component({
2901
+ * selector: 'app-date-picker',
2902
+ * providers: [{
2903
+ * provide: NG_VALUE_ACCESSOR,
2904
+ * useExisting: forwardRef(() => DatePickerComponent),
2905
+ * multi: true,
2906
+ * }],
2907
+ * template: `
2908
+ * <input
2909
+ * type="date"
2910
+ * [value]="displayValue()"
2911
+ * [disabled]="disabledState()"
2912
+ * (change)="commitBoth($event.target.value)"
2913
+ * (keydown.escape)="cancelEdit()"
2914
+ * />
2915
+ * `
2916
+ * })
2917
+ * export class DatePickerComponent extends BaseGridEditorCVA<MyRow, string> {}
2918
+ * ```
2919
+ *
2920
+ * > **Note:** Subclasses must still provide `NG_VALUE_ACCESSOR` themselves
2921
+ * > because `forwardRef(() => ConcreteClass)` must reference the concrete
2922
+ * > component — this is an Angular limitation.
2923
+ *
2924
+ * @typeParam TRow - The row data type
2925
+ * @typeParam TValue - The cell/control value type
2926
+ */
2927
+ class BaseGridEditorCVA extends BaseGridEditor {
2928
+ // ============================================================================
2929
+ // CVA State
2930
+ // ============================================================================
2931
+ /** Internal onChange callback registered by the form control. */
2932
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
2933
+ _onChange = () => { };
2934
+ /** Internal onTouched callback registered by the form control. */
2935
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
2936
+ _onTouched = () => { };
2937
+ /**
2938
+ * Signal holding the value written by the form control via `writeValue()`.
2939
+ * Updated when the form control pushes a new value (e.g. `patchValue`, `setValue`).
2940
+ */
2941
+ cvaValue = signal(null, ...(ngDevMode ? [{ debugName: "cvaValue" }] : []));
2942
+ /**
2943
+ * Signal tracking the disabled state set by the form control.
2944
+ * Updated when `setDisabledState()` is called by Angular's forms module.
2945
+ */
2946
+ disabledState = signal(false, ...(ngDevMode ? [{ debugName: "disabledState" }] : []));
2947
+ /**
2948
+ * Resolved display value.
2949
+ *
2950
+ * Prefers `currentValue()` (grid context — from `control.value` or `value` input)
2951
+ * and falls back to `cvaValue()` (standalone form context — from `writeValue`).
2952
+ *
2953
+ * Use this in your template instead of reading `currentValue()` directly
2954
+ * so the component works in both grid and standalone form contexts.
2955
+ */
2956
+ displayValue = computed(() => {
2957
+ return this.currentValue() ?? this.cvaValue();
2958
+ }, ...(ngDevMode ? [{ debugName: "displayValue" }] : []));
2959
+ // ============================================================================
2960
+ // ControlValueAccessor Implementation
2961
+ // ============================================================================
2962
+ /**
2963
+ * Called by Angular forms when the form control value changes programmatically.
2964
+ */
2965
+ writeValue(value) {
2966
+ this.cvaValue.set(value);
2967
+ }
2968
+ /**
2969
+ * Called by Angular forms to register a change callback.
2970
+ */
2971
+ registerOnChange(fn) {
2972
+ this._onChange = fn;
2973
+ }
2974
+ /**
2975
+ * Called by Angular forms to register a touched callback.
2976
+ */
2977
+ registerOnTouched(fn) {
2978
+ this._onTouched = fn;
2979
+ }
2980
+ /**
2981
+ * Called by Angular forms to set the disabled state.
2982
+ */
2983
+ setDisabledState(isDisabled) {
2984
+ this.disabledState.set(isDisabled);
2985
+ }
2986
+ // ============================================================================
2987
+ // Dual-Commit Helpers
2988
+ // ============================================================================
2989
+ /**
2990
+ * Commit a value through both the CVA (form control) and the grid.
2991
+ *
2992
+ * - Calls the CVA `onChange` callback (updates the form control)
2993
+ * - Marks the control as touched
2994
+ * - Calls `commitValue()` (emits grid commit event + DOM `CustomEvent`)
2995
+ *
2996
+ * Use this instead of `commitValue()` when your editor doubles as a form control.
2997
+ *
2998
+ * @param value - The new value to commit
2999
+ */
3000
+ commitBoth(value) {
3001
+ // Update CVA
3002
+ this.cvaValue.set(value);
3003
+ this._onChange(value);
3004
+ this._onTouched();
3005
+ // Update grid
3006
+ if (value != null) {
3007
+ this.commitValue(value);
3008
+ }
3009
+ }
3010
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BaseGridEditorCVA, deps: null, target: i0.ɵɵFactoryTarget.Directive });
3011
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.1", type: BaseGridEditorCVA, isStandalone: true, usesInheritance: true, ngImport: i0 });
3012
+ }
3013
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BaseGridEditorCVA, decorators: [{
3014
+ type: Directive
3015
+ }] });
3016
+
3017
+ // #endregion
3018
+ // #region Global Styles
3019
+ /** Tracks whether the global overlay stylesheet has been injected. */
3020
+ let overlayStylesInjected = false;
3021
+ /**
3022
+ * CSS for the overlay panel base layer.
3023
+ * Injected once into `<head>` on first `BaseOverlayEditor` use.
3024
+ *
3025
+ * Uses CSS Anchor Positioning as primary strategy with a JS fallback
3026
+ * for browsers that don't support it (Firefox, Safari as of late 2025).
3027
+ */
3028
+ const OVERLAY_STYLES = /* css */ `
3029
+ .tbw-overlay-panel {
3030
+ position: fixed;
3031
+ z-index: 10000;
3032
+ background: var(--tbw-overlay-bg, #fff);
3033
+ border: 1px solid var(--tbw-overlay-border, #ccc);
3034
+ border-radius: var(--tbw-overlay-radius, 4px);
3035
+ box-shadow: var(--tbw-overlay-shadow, 0 4px 12px rgba(0, 0, 0, 0.15));
3036
+ box-sizing: border-box;
3037
+ overflow: auto;
3038
+ }
3039
+
3040
+ .tbw-overlay-panel:popover-open {
3041
+ display: block;
3042
+ }
3043
+
3044
+ @supports (anchor-name: --a) {
3045
+ .tbw-overlay-panel[data-anchor-id] {
3046
+ position: fixed;
3047
+ position-anchor: var(--tbw-overlay-anchor);
3048
+ inset: unset;
3049
+ }
3050
+ .tbw-overlay-panel[data-pos="below"] {
3051
+ top: anchor(bottom);
3052
+ left: anchor(left);
3053
+ position-try-fallbacks: flip-block;
3054
+ }
3055
+ .tbw-overlay-panel[data-pos="above"] {
3056
+ bottom: anchor(top);
3057
+ left: anchor(left);
3058
+ position-try-fallbacks: flip-block;
3059
+ }
3060
+ .tbw-overlay-panel[data-pos="below-right"] {
3061
+ top: anchor(bottom);
3062
+ right: anchor(right);
3063
+ position-try-fallbacks: flip-block;
3064
+ }
3065
+ .tbw-overlay-panel[data-pos="over-top-left"] {
3066
+ top: anchor(top);
3067
+ left: anchor(left);
3068
+ }
3069
+ .tbw-overlay-panel[data-pos="over-bottom-left"] {
3070
+ bottom: anchor(bottom);
3071
+ left: anchor(left);
3072
+ }
3073
+ }
3074
+ `;
3075
+ function ensureOverlayStyles() {
3076
+ if (overlayStylesInjected)
3077
+ return;
3078
+ overlayStylesInjected = true;
3079
+ const style = document.createElement('style');
3080
+ style.setAttribute('data-tbw-overlay', '');
3081
+ style.textContent = OVERLAY_STYLES;
3082
+ document.head.appendChild(style);
3083
+ }
3084
+ // #endregion
3085
+ // #region Anchor ID Counter
3086
+ let anchorCounter = 0;
3087
+ // #endregion
3088
+ /**
3089
+ * Base class for grid editors that display a floating overlay panel.
3090
+ *
3091
+ * Provides infrastructure for:
3092
+ * - **Overlay positioning** — CSS Anchor Positioning with JS fallback
3093
+ * - **Focus gating** — in row editing mode, the panel only opens for the focused cell
3094
+ * - **Click-outside detection** — closes the panel when clicking outside
3095
+ * - **MutationObserver** — detects cell focus changes (row editing mode)
3096
+ * - **Escape handling** — closes the panel and returns focus to the inline input
3097
+ * - **Synthetic Tab dispatch** — advances grid focus after overlay close
3098
+ * - **Automatic teardown** — removes the panel from `<body>` and cleans up listeners
3099
+ *
3100
+ * ## Usage
3101
+ *
3102
+ * ```typescript
3103
+ * import { Component, ViewChild, ElementRef } from '@angular/core';
3104
+ * import { BaseOverlayEditor } from '@toolbox-web/grid-angular';
3105
+ *
3106
+ * @Component({
3107
+ * selector: 'app-date-editor',
3108
+ * template: `
3109
+ * <input
3110
+ * #inlineInput
3111
+ * readonly
3112
+ * [value]="currentValue()"
3113
+ * (click)="onInlineClick()"
3114
+ * (keydown)="onInlineKeydown($event)"
3115
+ * />
3116
+ * <div #panel class="tbw-overlay-panel" style="width: 280px;">
3117
+ * <!-- your date picker UI here -->
3118
+ * <div class="actions">
3119
+ * <button (click)="selectAndClose(selectedDate)">OK</button>
3120
+ * <button (click)="hideOverlay()">Cancel</button>
3121
+ * </div>
3122
+ * </div>
3123
+ * `
3124
+ * })
3125
+ * export class DateEditorComponent extends BaseOverlayEditor<MyRow, string> {
3126
+ * @ViewChild('panel') panelRef!: ElementRef<HTMLElement>;
3127
+ * @ViewChild('inlineInput') inputRef!: ElementRef<HTMLInputElement>;
3128
+ *
3129
+ * protected override overlayPosition = 'below' as const;
3130
+ *
3131
+ * ngAfterViewInit(): void {
3132
+ * this.initOverlay(this.panelRef.nativeElement);
3133
+ * if (this.isCellFocused()) this.showOverlay();
3134
+ * }
3135
+ *
3136
+ * protected getInlineInput(): HTMLInputElement | null {
3137
+ * return this.inputRef?.nativeElement ?? null;
3138
+ * }
3139
+ *
3140
+ * protected onOverlayOutsideClick(): void {
3141
+ * this.hideOverlay();
3142
+ * }
3143
+ *
3144
+ * selectAndClose(date: string): void {
3145
+ * this.commitValue(date);
3146
+ * this.hideOverlay();
3147
+ * }
3148
+ * }
3149
+ * ```
3150
+ *
3151
+ * @typeParam TRow - The row data type
3152
+ * @typeParam TValue - The cell value type
3153
+ */
3154
+ class BaseOverlayEditor extends BaseGridEditor {
3155
+ _elementRef = inject(ElementRef);
3156
+ _overlayDestroyRef = inject(DestroyRef);
3157
+ // ============================================================================
3158
+ // Configuration
3159
+ // ============================================================================
3160
+ /**
3161
+ * Position of the overlay panel relative to the anchor cell.
3162
+ * Override in subclasses to change the default position.
3163
+ *
3164
+ * @default 'below'
3165
+ */
3166
+ overlayPosition = 'below';
3167
+ // ============================================================================
3168
+ // Internal State
3169
+ // ============================================================================
3170
+ /** The overlay panel element (set via `initOverlay()`). */
3171
+ _panel = null;
3172
+ /** Whether the overlay is currently visible. */
3173
+ _isOpen = false;
3174
+ /** Unique anchor ID for CSS Anchor Positioning. */
3175
+ _anchorId = '';
3176
+ /** Whether the browser supports CSS Anchor Positioning. */
3177
+ _supportsAnchor = false;
3178
+ /** AbortController for all overlay-related listeners. */
3179
+ _abortCtrl = null;
3180
+ /** MutationObserver watching cell focus class changes. */
3181
+ _focusObserver = null;
3182
+ // ============================================================================
3183
+ // Lifecycle
3184
+ // ============================================================================
3185
+ constructor() {
3186
+ super();
3187
+ this._supportsAnchor = typeof CSS !== 'undefined' && CSS.supports('anchor-name', '--a');
3188
+ ensureOverlayStyles();
3189
+ afterNextRender(() => this._setupFocusObserver());
3190
+ this._overlayDestroyRef.onDestroy(() => this.teardownOverlay());
3191
+ }
3192
+ // ============================================================================
3193
+ // Public API — Subclass Interface
3194
+ // ============================================================================
3195
+ /**
3196
+ * Initialise the overlay with the panel element.
3197
+ *
3198
+ * Call this in `ngAfterViewInit` with your `@ViewChild` panel reference.
3199
+ * The panel is moved to `<body>` and hidden until {@link showOverlay} is called.
3200
+ *
3201
+ * @param panel - The overlay panel DOM element
3202
+ */
3203
+ initOverlay(panel) {
3204
+ this._panel = panel;
3205
+ // Assign a unique anchor ID
3206
+ this._anchorId = `tbw-anchor-${++anchorCounter}`;
3207
+ panel.classList.add('tbw-overlay-panel');
3208
+ panel.setAttribute('data-pos', this.overlayPosition);
3209
+ panel.setAttribute('data-anchor-id', this._anchorId);
3210
+ panel.style.display = 'none';
3211
+ // Set up CSS Anchor Positioning on the cell
3212
+ if (this._supportsAnchor) {
3213
+ const cell = this._getCell();
3214
+ if (cell) {
3215
+ cell.style.setProperty('anchor-name', `--${this._anchorId}`);
3216
+ panel.style.setProperty('--tbw-overlay-anchor', `--${this._anchorId}`);
3217
+ }
3218
+ }
3219
+ // Move panel to body so it escapes grid overflow clipping
3220
+ document.body.appendChild(panel);
3221
+ // Set up click-outside detection
3222
+ this._abortCtrl = new AbortController();
3223
+ document.addEventListener('pointerdown', (e) => this._onDocumentPointerDown(e), {
3224
+ signal: this._abortCtrl.signal,
3225
+ });
3226
+ }
3227
+ /**
3228
+ * Show the overlay panel.
3229
+ *
3230
+ * If CSS Anchor Positioning is not supported, falls back to JS-based
3231
+ * positioning using `getBoundingClientRect()`.
3232
+ */
3233
+ showOverlay() {
3234
+ if (!this._panel || this._isOpen)
3235
+ return;
3236
+ this._isOpen = true;
3237
+ this._panel.style.display = '';
3238
+ // JS fallback positioning for browsers without CSS Anchor Positioning
3239
+ if (!this._supportsAnchor) {
3240
+ this._positionWithJs();
3241
+ }
3242
+ }
3243
+ /**
3244
+ * Hide the overlay panel.
3245
+ *
3246
+ * @param suppressTabAdvance - When `true`, skip synthetic Tab dispatch
3247
+ * (useful when hiding is triggered by an external focus change).
3248
+ */
3249
+ hideOverlay(suppressTabAdvance) {
3250
+ if (!this._panel || !this._isOpen)
3251
+ return;
3252
+ this._isOpen = false;
3253
+ this._panel.style.display = 'none';
3254
+ if (!suppressTabAdvance) {
3255
+ this.getInlineInput()?.focus();
3256
+ }
3257
+ }
3258
+ /**
3259
+ * Close and immediately re-open the overlay.
3260
+ * Useful after the panel content changes size and needs repositioning.
3261
+ */
3262
+ reopenOverlay() {
3263
+ if (!this._panel)
3264
+ return;
3265
+ this._isOpen = false;
3266
+ this._panel.style.display = 'none';
3267
+ this.showOverlay();
3268
+ }
3269
+ /**
3270
+ * Remove the overlay from the DOM and clean up all listeners.
3271
+ *
3272
+ * Called automatically on `DestroyRef.onDestroy`. Can also be called
3273
+ * manually if the editor needs early cleanup.
3274
+ */
3275
+ teardownOverlay() {
3276
+ this._abortCtrl?.abort();
3277
+ this._abortCtrl = null;
3278
+ this._focusObserver?.disconnect();
3279
+ this._focusObserver = null;
3280
+ if (this._panel?.parentNode) {
3281
+ this._panel.parentNode.removeChild(this._panel);
3282
+ }
3283
+ this._panel = null;
3284
+ this._isOpen = false;
3285
+ // Clean up anchor-name on the cell
3286
+ if (this._supportsAnchor) {
3287
+ const cell = this._getCell();
3288
+ if (cell) {
3289
+ cell.style.removeProperty('anchor-name');
3290
+ }
3291
+ }
3292
+ }
3293
+ /**
3294
+ * Override in `edit-close` handler to also hide the overlay.
3295
+ * This is called automatically by `BaseGridEditor` when the grid
3296
+ * ends the editing session.
3297
+ */
3298
+ onEditClose() {
3299
+ this.hideOverlay(true);
3300
+ }
3301
+ // ============================================================================
3302
+ // Keyboard & Click Helpers
3303
+ // ============================================================================
3304
+ /**
3305
+ * Keydown handler for the inline readonly input.
3306
+ *
3307
+ * - **Enter / Space / ArrowDown / F2** → open overlay
3308
+ * - **Escape** → calls {@link handleEscape}
3309
+ *
3310
+ * Bind this to `(keydown)` on your inline input element.
3311
+ */
3312
+ onInlineKeydown(event) {
3313
+ switch (event.key) {
3314
+ case 'Enter':
3315
+ case ' ':
3316
+ case 'ArrowDown':
3317
+ case 'F2':
3318
+ event.preventDefault();
3319
+ this.showOverlay();
3320
+ this.onOverlayOpened();
3321
+ break;
3322
+ case 'Escape':
3323
+ this.handleEscape(event);
3324
+ break;
3325
+ }
3326
+ }
3327
+ /**
3328
+ * Click handler for the inline input.
3329
+ * Opens the overlay and calls {@link onOverlayOpened}.
3330
+ *
3331
+ * Bind this to `(click)` on your inline input element.
3332
+ */
3333
+ onInlineClick() {
3334
+ if (this._isOpen) {
3335
+ this.hideOverlay();
3336
+ }
3337
+ else {
3338
+ this.showOverlay();
3339
+ this.onOverlayOpened();
3340
+ }
3341
+ }
3342
+ /**
3343
+ * Handle Escape key press.
3344
+ *
3345
+ * If the overlay is open, closes it and returns focus to the inline input.
3346
+ * If the overlay is already closed, cancels the edit entirely.
3347
+ */
3348
+ handleEscape(event) {
3349
+ if (this._isOpen) {
3350
+ event.stopPropagation();
3351
+ this.hideOverlay();
3352
+ }
3353
+ else {
3354
+ this.cancelEdit();
3355
+ }
3356
+ }
3357
+ /**
3358
+ * Dispatch a synthetic Tab key event to advance grid focus.
3359
+ *
3360
+ * Call this after committing a value and closing the overlay so the
3361
+ * grid moves focus to the next cell.
3362
+ *
3363
+ * @param backward - When `true`, dispatch Shift+Tab to move backwards.
3364
+ */
3365
+ advanceGridFocus(backward = false) {
3366
+ const cell = this._getCell();
3367
+ if (!cell)
3368
+ return;
3369
+ cell.dispatchEvent(new KeyboardEvent('keydown', {
3370
+ key: 'Tab',
3371
+ shiftKey: backward,
3372
+ bubbles: true,
3373
+ cancelable: true,
3374
+ }));
3375
+ }
3376
+ /**
3377
+ * Called after the overlay is shown.
3378
+ *
3379
+ * Override to focus an element inside the panel, start animations, etc.
3380
+ * Default implementation is a no-op.
3381
+ */
3382
+ onOverlayOpened() {
3383
+ // Default: no-op. Subclasses override.
3384
+ }
3385
+ // ============================================================================
3386
+ // Private Helpers
3387
+ // ============================================================================
3388
+ /** Find the parent cell element for this editor. */
3389
+ _getCell() {
3390
+ return this._elementRef.nativeElement.closest('[part="cell"]') ?? null;
3391
+ }
3392
+ /**
3393
+ * JS fallback positioning for browsers without CSS Anchor Positioning.
3394
+ * Uses `getBoundingClientRect()` with viewport overflow detection.
3395
+ */
3396
+ _positionWithJs() {
3397
+ const cell = this._getCell();
3398
+ const panel = this._panel;
3399
+ if (!cell || !panel)
3400
+ return;
3401
+ const cellRect = cell.getBoundingClientRect();
3402
+ // Temporarily make visible to measure
3403
+ panel.style.visibility = 'hidden';
3404
+ panel.style.display = '';
3405
+ const panelRect = panel.getBoundingClientRect();
3406
+ panel.style.visibility = '';
3407
+ const viewportH = window.innerHeight;
3408
+ const viewportW = window.innerWidth;
3409
+ let top;
3410
+ let left;
3411
+ switch (this.overlayPosition) {
3412
+ case 'above': {
3413
+ top = cellRect.top - panelRect.height;
3414
+ left = cellRect.left;
3415
+ // Flip to below if off-screen
3416
+ if (top < 0)
3417
+ top = cellRect.bottom;
3418
+ break;
3419
+ }
3420
+ case 'below-right': {
3421
+ top = cellRect.bottom;
3422
+ left = cellRect.right - panelRect.width;
3423
+ // Flip to above if off-screen
3424
+ if (top + panelRect.height > viewportH)
3425
+ top = cellRect.top - panelRect.height;
3426
+ break;
3427
+ }
3428
+ case 'over-top-left': {
3429
+ top = cellRect.top;
3430
+ left = cellRect.left;
3431
+ break;
3432
+ }
3433
+ case 'over-bottom-left': {
3434
+ top = cellRect.bottom - panelRect.height;
3435
+ left = cellRect.left;
3436
+ break;
3437
+ }
3438
+ case 'below':
3439
+ default: {
3440
+ top = cellRect.bottom;
3441
+ left = cellRect.left;
3442
+ // Flip to above if off-screen
3443
+ if (top + panelRect.height > viewportH)
3444
+ top = cellRect.top - panelRect.height;
3445
+ break;
3446
+ }
3447
+ }
3448
+ // Clamp to viewport
3449
+ if (left + panelRect.width > viewportW)
3450
+ left = viewportW - panelRect.width - 4;
3451
+ if (left < 0)
3452
+ left = 4;
3453
+ if (top < 0)
3454
+ top = 4;
3455
+ panel.style.top = `${top}px`;
3456
+ panel.style.left = `${left}px`;
3457
+ }
3458
+ /**
3459
+ * Document pointerdown handler for click-outside detection.
3460
+ * Fires `onOverlayOutsideClick()` if the click is outside the panel
3461
+ * and outside the editor's host element.
3462
+ */
3463
+ _onDocumentPointerDown(event) {
3464
+ if (!this._isOpen || !this._panel)
3465
+ return;
3466
+ const target = event.target;
3467
+ const hostEl = this._elementRef.nativeElement;
3468
+ // Click inside panel or host — ignore
3469
+ if (this._panel.contains(target) || hostEl.contains(target))
3470
+ return;
3471
+ this.onOverlayOutsideClick();
3472
+ }
3473
+ /**
3474
+ * Set up a MutationObserver on the parent cell to watch for
3475
+ * `cell-focus` class changes. This handles row-editing mode where
3476
+ * all editors exist simultaneously but only the focused cell's
3477
+ * editor should have its overlay visible.
3478
+ */
3479
+ _setupFocusObserver() {
3480
+ const cell = this._getCell();
3481
+ if (!cell)
3482
+ return;
3483
+ this._focusObserver = new MutationObserver((mutations) => {
3484
+ for (const mutation of mutations) {
3485
+ if (mutation.type !== 'attributes' || mutation.attributeName !== 'class')
3486
+ continue;
3487
+ const isFocused = cell.classList.contains('cell-focus');
3488
+ if (isFocused && !this._isOpen) {
3489
+ // Cell just gained focus — open overlay if appropriate
3490
+ this.showOverlay();
3491
+ this.onOverlayOpened();
3492
+ }
3493
+ else if (!isFocused && this._isOpen) {
3494
+ // Cell lost focus — hide overlay silently
3495
+ this.hideOverlay(true);
3496
+ }
3497
+ }
3498
+ });
3499
+ this._focusObserver.observe(cell, { attributes: true, attributeFilter: ['class'] });
3500
+ }
3501
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BaseOverlayEditor, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3502
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.1", type: BaseOverlayEditor, isStandalone: true, usesInheritance: true, ngImport: i0 });
3503
+ }
3504
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BaseOverlayEditor, decorators: [{
3505
+ type: Directive
3506
+ }], ctorParameters: () => [] });
3507
+
3508
+ /**
3509
+ * Directive that registers `<tbw-grid-column>` as a known Angular element.
3510
+ *
3511
+ * This directive exists so that Angular's template compiler recognises
3512
+ * `<tbw-grid-column>` without requiring `CUSTOM_ELEMENTS_SCHEMA`.
3513
+ * The underlying web component reads its attributes (`field`, `header`,
3514
+ * `type`, `width`, etc.) directly from the DOM, so no `@Input()` forwarding
3515
+ * is needed — Angular's standard property/attribute binding handles it.
3516
+ *
3517
+ * ## Usage
3518
+ *
3519
+ * ```typescript
3520
+ * import { Component } from '@angular/core';
3521
+ * import { Grid, TbwGridColumn, TbwRenderer } from '@toolbox-web/grid-angular';
3522
+ *
3523
+ * @Component({
3524
+ * imports: [Grid, TbwGridColumn, TbwRenderer],
3525
+ * template: `
3526
+ * <tbw-grid [rows]="rows" [gridConfig]="config">
3527
+ * <tbw-grid-column field="status">
3528
+ * <app-status-badge *tbwRenderer="let value" [value]="value" />
3529
+ * </tbw-grid-column>
3530
+ * </tbw-grid>
3531
+ * `
3532
+ * })
3533
+ * export class MyComponent { }
3534
+ * ```
3535
+ */
3536
+ class TbwGridColumn {
3537
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TbwGridColumn, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3538
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.1", type: TbwGridColumn, isStandalone: true, selector: "tbw-grid-column", ngImport: i0 });
3539
+ }
3540
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TbwGridColumn, decorators: [{
3541
+ type: Directive,
3542
+ args: [{
3543
+ selector: 'tbw-grid-column',
3544
+ }]
3545
+ }] });
3546
+
3547
+ /**
3548
+ * Directive that registers `<tbw-grid-header>` as a known Angular element.
3549
+ *
3550
+ * This directive exists so that Angular's template compiler recognises
3551
+ * `<tbw-grid-header>` without requiring `CUSTOM_ELEMENTS_SCHEMA`.
3552
+ * The grid's `config-manager` reads attributes like `title` directly
3553
+ * from the DOM element.
3554
+ *
3555
+ * ## Usage
3556
+ *
3557
+ * ```typescript
3558
+ * import { Component } from '@angular/core';
3559
+ * import { Grid, TbwGridHeader } from '@toolbox-web/grid-angular';
3560
+ *
3561
+ * @Component({
3562
+ * imports: [Grid, TbwGridHeader],
3563
+ * template: `
3564
+ * <tbw-grid [rows]="rows" [gridConfig]="config">
3565
+ * <tbw-grid-header title="My Grid Title"></tbw-grid-header>
3566
+ * </tbw-grid>
3567
+ * `
3568
+ * })
3569
+ * export class MyComponent { }
3570
+ * ```
3571
+ */
3572
+ class TbwGridHeader {
3573
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TbwGridHeader, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3574
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.1", type: TbwGridHeader, isStandalone: true, selector: "tbw-grid-header", ngImport: i0 });
3575
+ }
3576
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TbwGridHeader, decorators: [{
3577
+ type: Directive,
3578
+ args: [{
3579
+ selector: 'tbw-grid-header',
3580
+ }]
3581
+ }] });
3582
+
2702
3583
  // Symbol for storing form context on the grid element (shared with GridFormArray)
2703
3584
  const FORM_ARRAY_CONTEXT = Symbol('formArrayContext');
2704
3585
  /**
@@ -3183,6 +4064,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
3183
4064
  }]
3184
4065
  }], 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"] }] } });
3185
4066
 
4067
+ /**
4068
+ * Directive that registers `<tbw-grid-tool-buttons>` as a known Angular element.
4069
+ *
4070
+ * This directive exists so that Angular's template compiler recognises
4071
+ * `<tbw-grid-tool-buttons>` without requiring `CUSTOM_ELEMENTS_SCHEMA`.
4072
+ * The grid's shell reads toolbar buttons directly from the DOM.
4073
+ *
4074
+ * ## Usage
4075
+ *
4076
+ * ```typescript
4077
+ * import { Component } from '@angular/core';
4078
+ * import { Grid, TbwGridToolButtons } from '@toolbox-web/grid-angular';
4079
+ *
4080
+ * @Component({
4081
+ * imports: [Grid, TbwGridToolButtons],
4082
+ * template: `
4083
+ * <tbw-grid [rows]="rows" [gridConfig]="config">
4084
+ * <tbw-grid-tool-buttons>
4085
+ * <button (click)="doSomething()">Action</button>
4086
+ * </tbw-grid-tool-buttons>
4087
+ * </tbw-grid>
4088
+ * `
4089
+ * })
4090
+ * export class MyComponent { }
4091
+ * ```
4092
+ */
4093
+ class TbwGridToolButtons {
4094
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TbwGridToolButtons, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4095
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.1", type: TbwGridToolButtons, isStandalone: true, selector: "tbw-grid-tool-buttons", ngImport: i0 });
4096
+ }
4097
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: TbwGridToolButtons, decorators: [{
4098
+ type: Directive,
4099
+ args: [{
4100
+ selector: 'tbw-grid-tool-buttons',
4101
+ }]
4102
+ }] });
4103
+
3186
4104
  /**
3187
4105
  * Directive that automatically registers the Angular adapter with tbw-grid elements.
3188
4106
  *
@@ -3192,13 +4110,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
3192
4110
  * ## Usage
3193
4111
  *
3194
4112
  * ```typescript
3195
- * import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
4113
+ * import { Component } from '@angular/core';
3196
4114
  * import { Grid } from '@toolbox-web/grid-angular';
3197
4115
  *
3198
4116
  * @Component({
3199
4117
  * selector: 'app-root',
3200
4118
  * imports: [Grid],
3201
- * schemas: [CUSTOM_ELEMENTS_SCHEMA],
3202
4119
  * template: `
3203
4120
  * <tbw-grid [rows]="rows" [gridConfig]="config" [customStyles]="myStyles">
3204
4121
  * <!-- column templates -->
@@ -3292,6 +4209,30 @@ class Grid {
3292
4209
  const grid = this.elementRef.nativeElement;
3293
4210
  grid.loading = loadingValue;
3294
4211
  });
4212
+ // Effect to sync rows to the grid element
4213
+ effect(() => {
4214
+ const rowsValue = this.rows();
4215
+ if (rowsValue === undefined)
4216
+ return;
4217
+ const grid = this.elementRef.nativeElement;
4218
+ grid.rows = rowsValue;
4219
+ });
4220
+ // Effect to sync columns to the grid element
4221
+ effect(() => {
4222
+ const columnsValue = this.columns();
4223
+ if (columnsValue === undefined)
4224
+ return;
4225
+ const grid = this.elementRef.nativeElement;
4226
+ grid.columns = columnsValue;
4227
+ });
4228
+ // Effect to sync fitMode to the grid element
4229
+ effect(() => {
4230
+ const fitModeValue = this.fitMode();
4231
+ if (fitModeValue === undefined)
4232
+ return;
4233
+ const grid = this.elementRef.nativeElement;
4234
+ grid.fitMode = fitModeValue;
4235
+ });
3295
4236
  }
3296
4237
  /**
3297
4238
  * Custom CSS styles to inject into the grid.
@@ -3400,6 +4341,53 @@ class Grid {
3400
4341
  * ```
3401
4342
  */
3402
4343
  loading = input(...(ngDevMode ? [undefined, { debugName: "loading" }] : []));
4344
+ /**
4345
+ * The data rows to display in the grid.
4346
+ *
4347
+ * Accepts an array of data objects. Each object represents one row.
4348
+ * The grid reads property values for each column's `field` from these objects.
4349
+ *
4350
+ * @example
4351
+ * ```html
4352
+ * <tbw-grid [rows]="employees()" [gridConfig]="config" />
4353
+ * ```
4354
+ */
4355
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4356
+ rows = input(...(ngDevMode ? [undefined, { debugName: "rows" }] : []));
4357
+ /**
4358
+ * Column configuration array.
4359
+ *
4360
+ * Shorthand for setting columns without wrapping them in a full `gridConfig`.
4361
+ * If both `columns` and `gridConfig.columns` are set, `columns` takes precedence
4362
+ * (see configuration precedence system).
4363
+ *
4364
+ * @example
4365
+ * ```html
4366
+ * <tbw-grid [rows]="data" [columns]="[
4367
+ * { field: 'id', header: 'ID', pinned: 'left', width: 80 },
4368
+ * { field: 'name', header: 'Name' },
4369
+ * { field: 'email', header: 'Email' }
4370
+ * ]" />
4371
+ * ```
4372
+ */
4373
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4374
+ columns = input(...(ngDevMode ? [undefined, { debugName: "columns" }] : []));
4375
+ /**
4376
+ * Column sizing strategy.
4377
+ *
4378
+ * - `'stretch'` (default) — columns stretch to fill available width
4379
+ * - `'fixed'` — columns use their declared widths; enables horizontal scrolling
4380
+ * - `'auto-fit'` — columns auto-size to content, then stretch to fill
4381
+ *
4382
+ * @default 'stretch'
4383
+ *
4384
+ * @example
4385
+ * ```html
4386
+ * <tbw-grid [rows]="data" fitMode="fixed" />
4387
+ * <tbw-grid [rows]="data" [fitMode]="dynamicMode()" />
4388
+ * ```
4389
+ */
4390
+ fitMode = input(...(ngDevMode ? [undefined, { debugName: "fitMode" }] : []));
3403
4391
  /**
3404
4392
  * Grid configuration object with optional Angular-specific extensions.
3405
4393
  *
@@ -3618,9 +4606,9 @@ class Grid {
3618
4606
  * @example
3619
4607
  * ```html
3620
4608
  * <tbw-grid [pinnedColumns]="true" [columns]="[
3621
- * { field: 'id', sticky: 'left' },
4609
+ * { field: 'id', pinned: 'left' },
3622
4610
  * { field: 'name' },
3623
- * { field: 'actions', sticky: 'right' }
4611
+ * { field: 'actions', pinned: 'right' }
3624
4612
  * ]" />
3625
4613
  * ```
3626
4614
  */
@@ -4330,12 +5318,12 @@ class Grid {
4330
5318
  }
4331
5319
  }
4332
5320
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Grid, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4333
- 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 });
5321
+ 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 }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, fitMode: { classPropertyName: "fitMode", publicName: "fitMode", 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 });
4334
5322
  }
4335
5323
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Grid, decorators: [{
4336
5324
  type: Directive,
4337
5325
  args: [{ selector: 'tbw-grid' }]
4338
- }], 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"] }] } });
5326
+ }], 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 }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], fitMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitMode", 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"] }] } });
4339
5327
 
4340
5328
  /**
4341
5329
  * @packageDocumentation
@@ -4349,5 +5337,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
4349
5337
  * Generated bundle index. Do not edit.
4350
5338
  */
4351
5339
 
4352
- 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 };
5340
+ export { AngularGridAdapter, BaseFilterPanel, BaseGridEditor, BaseGridEditorCVA, BaseOverlayEditor, GRID_ICONS, GRID_TYPE_DEFAULTS, Grid, GridAdapter, GridColumnEditor, GridColumnView, GridDetailView, GridFormArray, GridIconRegistry, GridLazyForm, GridResponsiveCard, GridToolPanel, GridTypeRegistry, TbwEditor as TbwCellEditor, TbwRenderer as TbwCellView, TbwEditor, TbwGridColumn, TbwGridHeader, TbwGridToolButtons, TbwRenderer, clearFeatureRegistry, createPluginFromFeature, getFeatureFactory, getFormArrayContext, getLazyFormContext, getRegisteredFeatures, injectGrid, isComponentClass, isFeatureRegistered, provideGridIcons, provideGridTypeDefaults, registerFeature };
4353
5341
  //# sourceMappingURL=toolbox-web-grid-angular.mjs.map