@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.
- package/README.md +98 -0
- package/fesm2022/toolbox-web-grid-angular-features-clipboard.mjs +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-clipboard.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-export.mjs +16 -1
- package/fesm2022/toolbox-web-grid-angular-features-export.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs +16 -1
- package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-pinned-columns.mjs +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-pinned-columns.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-print.mjs +16 -1
- package/fesm2022/toolbox-web-grid-angular-features-print.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-selection.mjs +35 -1
- package/fesm2022/toolbox-web-grid-angular-features-selection.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs +63 -5
- package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular.mjs +1002 -14
- package/fesm2022/toolbox-web-grid-angular.mjs.map +1 -1
- package/package.json +1 -1
- package/types/toolbox-web-grid-angular-features-undo-redo.d.ts +24 -4
- package/types/toolbox-web-grid-angular-features-undo-redo.d.ts.map +1 -1
- package/types/toolbox-web-grid-angular.d.ts +578 -9
- package/types/toolbox-web-grid-angular.d.ts.map +1 -1
|
@@ -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
|
|
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
|
-
//
|
|
1502
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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',
|
|
4609
|
+
* { field: 'id', pinned: 'left' },
|
|
3622
4610
|
* { field: 'name' },
|
|
3623
|
-
* { field: 'actions',
|
|
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
|