@toolbox-web/grid-angular 0.14.2 → 0.14.3
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 +9 -20
- package/fesm2022/toolbox-web-grid-angular-features-export.mjs +1 -2
- package/fesm2022/toolbox-web-grid-angular-features-export.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs +1 -2
- package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-print.mjs +1 -2
- package/fesm2022/toolbox-web-grid-angular-features-print.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-selection.mjs +1 -2
- package/fesm2022/toolbox-web-grid-angular-features-selection.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs +1 -2
- package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular.mjs +33 -4
- package/fesm2022/toolbox-web-grid-angular.mjs.map +1 -1
- package/package.json +1 -1
- package/types/toolbox-web-grid-angular-features-export.d.ts +1 -2
- package/types/toolbox-web-grid-angular-features-export.d.ts.map +1 -1
- package/types/toolbox-web-grid-angular-features-filtering.d.ts +1 -2
- package/types/toolbox-web-grid-angular-features-filtering.d.ts.map +1 -1
- package/types/toolbox-web-grid-angular-features-print.d.ts +1 -2
- package/types/toolbox-web-grid-angular-features-print.d.ts.map +1 -1
- package/types/toolbox-web-grid-angular-features-selection.d.ts +1 -2
- package/types/toolbox-web-grid-angular-features-selection.d.ts.map +1 -1
- package/types/toolbox-web-grid-angular-features-undo-redo.d.ts +1 -2
- package/types/toolbox-web-grid-angular-features-undo-redo.d.ts.map +1 -1
- package/types/toolbox-web-grid-angular.d.ts +21 -7
- package/types/toolbox-web-grid-angular.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -51,13 +51,12 @@ import '@toolbox-web/grid';
|
|
|
51
51
|
### 2. Use in Components
|
|
52
52
|
|
|
53
53
|
```typescript
|
|
54
|
-
import { Component
|
|
54
|
+
import { Component } from '@angular/core';
|
|
55
55
|
import { Grid } from '@toolbox-web/grid-angular';
|
|
56
56
|
|
|
57
57
|
@Component({
|
|
58
58
|
selector: 'app-my-grid',
|
|
59
59
|
imports: [Grid],
|
|
60
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
61
60
|
template: ` <tbw-grid [rows]="rows" [gridConfig]="config" style="height: 400px; display: block;"> </tbw-grid> `,
|
|
62
61
|
})
|
|
63
62
|
export class MyGridComponent {
|
|
@@ -85,12 +84,11 @@ The cleanest way to define custom renderers and editors is with structural direc
|
|
|
85
84
|
Use `*tbwRenderer` to customize how cell values are displayed:
|
|
86
85
|
|
|
87
86
|
```typescript
|
|
88
|
-
import { Component
|
|
87
|
+
import { Component } from '@angular/core';
|
|
89
88
|
import { Grid, TbwRenderer } from '@toolbox-web/grid-angular';
|
|
90
89
|
|
|
91
90
|
@Component({
|
|
92
91
|
imports: [Grid, TbwRenderer, StatusBadgeComponent],
|
|
93
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
94
92
|
template: `
|
|
95
93
|
<tbw-grid [rows]="rows" [gridConfig]="config">
|
|
96
94
|
<tbw-grid-column field="status">
|
|
@@ -115,7 +113,7 @@ export class MyGridComponent {}
|
|
|
115
113
|
Use `*tbwEditor` for custom cell editors. The adapter automatically listens for `commit` and `cancel` events from your component, so you don't need to manually wire up callbacks:
|
|
116
114
|
|
|
117
115
|
```typescript
|
|
118
|
-
import { Component,
|
|
116
|
+
import { Component, output } from '@angular/core';
|
|
119
117
|
import { Grid, TbwRenderer, TbwEditor } from '@toolbox-web/grid-angular';
|
|
120
118
|
|
|
121
119
|
// Your editor component just needs to emit 'commit' and 'cancel' events
|
|
@@ -136,7 +134,6 @@ export class StatusEditorComponent {
|
|
|
136
134
|
|
|
137
135
|
@Component({
|
|
138
136
|
imports: [Grid, TbwRenderer, TbwEditor, StatusBadgeComponent, StatusEditorComponent],
|
|
139
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
140
137
|
template: `
|
|
141
138
|
<tbw-grid [rows]="rows" [gridConfig]="config">
|
|
142
139
|
<tbw-grid-column field="status" editable>
|
|
@@ -173,7 +170,6 @@ import { Grid, GridColumnView } from '@toolbox-web/grid-angular';
|
|
|
173
170
|
|
|
174
171
|
@Component({
|
|
175
172
|
imports: [Grid, GridColumnView],
|
|
176
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
177
173
|
template: `
|
|
178
174
|
<tbw-grid [rows]="rows" [gridConfig]="config">
|
|
179
175
|
<tbw-grid-column field="status">
|
|
@@ -195,7 +191,6 @@ import { Grid, GridColumnView, GridColumnEditor } from '@toolbox-web/grid-angula
|
|
|
195
191
|
|
|
196
192
|
@Component({
|
|
197
193
|
imports: [Grid, GridColumnView, GridColumnEditor],
|
|
198
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
199
194
|
template: `
|
|
200
195
|
<tbw-grid [rows]="rows" [gridConfig]="config">
|
|
201
196
|
<tbw-grid-column field="status" editable>
|
|
@@ -331,14 +326,13 @@ Then any grid with columns using `type: 'country'` will automatically use the re
|
|
|
331
326
|
|
|
332
327
|
```typescript
|
|
333
328
|
// my-grid.component.ts
|
|
334
|
-
import { Component
|
|
329
|
+
import { Component } from '@angular/core';
|
|
335
330
|
import { Grid } from '@toolbox-web/grid-angular';
|
|
336
331
|
import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';
|
|
337
332
|
import type { GridConfig } from '@toolbox-web/grid';
|
|
338
333
|
|
|
339
334
|
@Component({
|
|
340
335
|
imports: [Grid],
|
|
341
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
342
336
|
template: `<tbw-grid [rows]="data" [gridConfig]="config" />`,
|
|
343
337
|
})
|
|
344
338
|
export class MyGridComponent {
|
|
@@ -485,14 +479,13 @@ export class BonusEditorComponent implements CellEditor<Employee, number> {
|
|
|
485
479
|
Use `GridConfig` with the `gridConfig` input for type-safe component references:
|
|
486
480
|
|
|
487
481
|
```typescript
|
|
488
|
-
import { Component
|
|
482
|
+
import { Component } from '@angular/core';
|
|
489
483
|
import { Grid, type GridConfig } from '@toolbox-web/grid-angular';
|
|
490
484
|
import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';
|
|
491
485
|
import { StatusBadgeComponent, BonusEditorComponent } from './components';
|
|
492
486
|
|
|
493
487
|
@Component({
|
|
494
488
|
imports: [Grid],
|
|
495
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
496
489
|
template: `<tbw-grid [gridConfig]="config" [rows]="employees" />`,
|
|
497
490
|
})
|
|
498
491
|
export class MyGridComponent {
|
|
@@ -523,14 +516,13 @@ Both interfaces also support an optional `column()` input for accessing the colu
|
|
|
523
516
|
Import plugins individually for smaller bundles:
|
|
524
517
|
|
|
525
518
|
```typescript
|
|
526
|
-
import { Component
|
|
519
|
+
import { Component } from '@angular/core';
|
|
527
520
|
import { Grid } from '@toolbox-web/grid-angular';
|
|
528
521
|
import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';
|
|
529
522
|
import { FilteringPlugin } from '@toolbox-web/grid/plugins/filtering';
|
|
530
523
|
|
|
531
524
|
@Component({
|
|
532
525
|
imports: [Grid],
|
|
533
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
534
526
|
template: `<tbw-grid [rows]="rows" [gridConfig]="config" />`,
|
|
535
527
|
})
|
|
536
528
|
export class MyGridComponent {
|
|
@@ -557,7 +549,7 @@ The grid can be used as an Angular form control with `formControlName` or `formC
|
|
|
557
549
|
### Basic Usage with FormControl
|
|
558
550
|
|
|
559
551
|
```typescript
|
|
560
|
-
import { Component
|
|
552
|
+
import { Component } from '@angular/core';
|
|
561
553
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
|
562
554
|
import { Grid, GridFormArray } from '@toolbox-web/grid-angular';
|
|
563
555
|
import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';
|
|
@@ -570,7 +562,6 @@ interface Employee {
|
|
|
570
562
|
|
|
571
563
|
@Component({
|
|
572
564
|
imports: [Grid, GridFormArray, ReactiveFormsModule],
|
|
573
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
574
565
|
template: `
|
|
575
566
|
<tbw-grid [formControl]="employeesControl" [gridConfig]="config" style="height: 400px; display: block;" />
|
|
576
567
|
|
|
@@ -600,14 +591,13 @@ export class MyComponent {
|
|
|
600
591
|
### Usage with FormGroup
|
|
601
592
|
|
|
602
593
|
```typescript
|
|
603
|
-
import { Component
|
|
594
|
+
import { Component } from '@angular/core';
|
|
604
595
|
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
605
596
|
import { Grid, GridFormArray } from '@toolbox-web/grid-angular';
|
|
606
597
|
import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';
|
|
607
598
|
|
|
608
599
|
@Component({
|
|
609
600
|
imports: [Grid, GridFormArray, ReactiveFormsModule],
|
|
610
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
611
601
|
template: `
|
|
612
602
|
<form [formGroup]="form">
|
|
613
603
|
<tbw-grid formControlName="employees" [gridConfig]="config" style="height: 400px; display: block;" />
|
|
@@ -690,7 +680,7 @@ tbw-grid.form-disabled {
|
|
|
690
680
|
For fine-grained control over validation and form state at the cell level, use a `FormArray` of `FormGroup`s. This approach exposes the `FormControl` for each cell in the editor context, allowing custom editors to bind directly:
|
|
691
681
|
|
|
692
682
|
```typescript
|
|
693
|
-
import { Component,
|
|
683
|
+
import { Component, input, output } from '@angular/core';
|
|
694
684
|
import { FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
695
685
|
import { Grid, GridFormArray, TbwEditor, TbwRenderer } from '@toolbox-web/grid-angular';
|
|
696
686
|
import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';
|
|
@@ -732,7 +722,6 @@ export class ValidatedInputComponent {
|
|
|
732
722
|
|
|
733
723
|
@Component({
|
|
734
724
|
imports: [Grid, GridFormArray, TbwRenderer, TbwEditor, ReactiveFormsModule, ValidatedInputComponent],
|
|
735
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
736
725
|
template: `
|
|
737
726
|
<tbw-grid [formControl]="employeesArray" [gridConfig]="config">
|
|
738
727
|
<tbw-grid-column field="age" editable>
|
|
@@ -49,7 +49,7 @@ registerFeature('export', (config) => {
|
|
|
49
49
|
*
|
|
50
50
|
* @example
|
|
51
51
|
* ```typescript
|
|
52
|
-
* import { Component
|
|
52
|
+
* import { Component } from '@angular/core';
|
|
53
53
|
* import { Grid } from '@toolbox-web/grid-angular';
|
|
54
54
|
* import '@toolbox-web/grid-angular/features/export';
|
|
55
55
|
* import { injectGridExport } from '@toolbox-web/grid-angular/features/export';
|
|
@@ -57,7 +57,6 @@ registerFeature('export', (config) => {
|
|
|
57
57
|
* @Component({
|
|
58
58
|
* selector: 'app-my-grid',
|
|
59
59
|
* imports: [Grid],
|
|
60
|
-
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
61
60
|
* template: `
|
|
62
61
|
* <button (click)="handleExport()">Export CSV</button>
|
|
63
62
|
* <tbw-grid [rows]="rows" [export]="true"></tbw-grid>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbox-web-grid-angular-features-export.mjs","sources":["../../../../libs/grid-angular/features/export/src/index.ts","../../../../libs/grid-angular/features/export/src/toolbox-web-grid-angular-features-export.ts"],"sourcesContent":["/**\n * Export feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `export` input on Grid directive.\n * Also exports `injectGridExport()` for programmatic export control.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/export';\n *\n * <tbw-grid [export]=\"true\" />\n * <tbw-grid [export]=\"{ fileName: 'data.csv' }\" />\n * ```\n *\n * @example Using injectGridExport\n * ```typescript\n * import { injectGridExport } from '@toolbox-web/grid-angular/features/export';\n *\n * @Component({...})\n * export class MyComponent {\n * private gridExport = injectGridExport();\n *\n * exportData() {\n * this.gridExport.exportToCsv('employees.csv');\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { registerFeature } from '@toolbox-web/grid-angular';\nimport { ExportPlugin, type ExportFormat, type ExportParams } from '@toolbox-web/grid/plugins/export';\n\nregisterFeature('export', (config) => {\n if (config === true) {\n return new ExportPlugin();\n }\n return new ExportPlugin(config ?? undefined);\n});\n\n/**\n * Export methods returned from injectGridExport.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n * This ensures it works with lazy-rendered tabs, conditional rendering, etc.\n */\nexport interface ExportMethods {\n /**\n * Export grid data to CSV file.\n * @param filename - Optional filename (defaults to 'export.csv')\n * @param params - Optional export parameters\n */\n exportToCsv: (filename?: string, params?: Partial<ExportParams>) => void;\n\n /**\n * Export grid data to Excel file (XML Spreadsheet format).\n * @param filename - Optional filename (defaults to 'export.xlsx')\n * @param params - Optional export parameters\n */\n exportToExcel: (filename?: string, params?: Partial<ExportParams>) => void;\n\n /**\n * Export grid data to JSON file.\n * @param filename - Optional filename (defaults to 'export.json')\n * @param params - Optional export parameters\n */\n exportToJson: (filename?: string, params?: Partial<ExportParams>) => void;\n\n /**\n * Check if an export is currently in progress.\n */\n isExporting: () => boolean;\n\n /**\n * Get information about the last export.\n */\n getLastExport: () => { format: ExportFormat; timestamp: Date } | null;\n\n /**\n * Signal indicating if grid is ready.\n * The grid is discovered lazily, so this updates when first method call succeeds.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic export control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization. This ensures it works reliably with:\n * - Lazy-rendered tabs\n * - Conditional rendering (*ngIf)\n * - Dynamic component loading\n *\n * @example\n * ```typescript\n * import { Component
|
|
1
|
+
{"version":3,"file":"toolbox-web-grid-angular-features-export.mjs","sources":["../../../../libs/grid-angular/features/export/src/index.ts","../../../../libs/grid-angular/features/export/src/toolbox-web-grid-angular-features-export.ts"],"sourcesContent":["/**\n * Export feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `export` input on Grid directive.\n * Also exports `injectGridExport()` for programmatic export control.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/export';\n *\n * <tbw-grid [export]=\"true\" />\n * <tbw-grid [export]=\"{ fileName: 'data.csv' }\" />\n * ```\n *\n * @example Using injectGridExport\n * ```typescript\n * import { injectGridExport } from '@toolbox-web/grid-angular/features/export';\n *\n * @Component({...})\n * export class MyComponent {\n * private gridExport = injectGridExport();\n *\n * exportData() {\n * this.gridExport.exportToCsv('employees.csv');\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { registerFeature } from '@toolbox-web/grid-angular';\nimport { ExportPlugin, type ExportFormat, type ExportParams } from '@toolbox-web/grid/plugins/export';\n\nregisterFeature('export', (config) => {\n if (config === true) {\n return new ExportPlugin();\n }\n return new ExportPlugin(config ?? undefined);\n});\n\n/**\n * Export methods returned from injectGridExport.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n * This ensures it works with lazy-rendered tabs, conditional rendering, etc.\n */\nexport interface ExportMethods {\n /**\n * Export grid data to CSV file.\n * @param filename - Optional filename (defaults to 'export.csv')\n * @param params - Optional export parameters\n */\n exportToCsv: (filename?: string, params?: Partial<ExportParams>) => void;\n\n /**\n * Export grid data to Excel file (XML Spreadsheet format).\n * @param filename - Optional filename (defaults to 'export.xlsx')\n * @param params - Optional export parameters\n */\n exportToExcel: (filename?: string, params?: Partial<ExportParams>) => void;\n\n /**\n * Export grid data to JSON file.\n * @param filename - Optional filename (defaults to 'export.json')\n * @param params - Optional export parameters\n */\n exportToJson: (filename?: string, params?: Partial<ExportParams>) => void;\n\n /**\n * Check if an export is currently in progress.\n */\n isExporting: () => boolean;\n\n /**\n * Get information about the last export.\n */\n getLastExport: () => { format: ExportFormat; timestamp: Date } | null;\n\n /**\n * Signal indicating if grid is ready.\n * The grid is discovered lazily, so this updates when first method call succeeds.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic export control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization. This ensures it works reliably with:\n * - Lazy-rendered tabs\n * - Conditional rendering (*ngIf)\n * - Dynamic component loading\n *\n * @example\n * ```typescript\n * import { Component } from '@angular/core';\n * import { Grid } from '@toolbox-web/grid-angular';\n * import '@toolbox-web/grid-angular/features/export';\n * import { injectGridExport } from '@toolbox-web/grid-angular/features/export';\n *\n * @Component({\n * selector: 'app-my-grid',\n * imports: [Grid],\n * template: `\n * <button (click)=\"handleExport()\">Export CSV</button>\n * <tbw-grid [rows]=\"rows\" [export]=\"true\"></tbw-grid>\n * `\n * })\n * export class MyGridComponent {\n * gridExport = injectGridExport();\n *\n * handleExport() {\n * this.gridExport.exportToCsv('employees.csv');\n * }\n * }\n * ```\n */\nexport function injectGridExport(): ExportMethods {\n const elementRef = inject(ElementRef);\n const destroyRef = inject(DestroyRef);\n const isReady = signal(false);\n\n // Lazy discovery: cached grid reference\n let cachedGrid: DataGridElement | null = null;\n let readyPromiseStarted = false;\n\n /**\n * Lazily find the grid element. Called on each method invocation.\n * Caches the reference once found and triggers ready() check.\n */\n const getGrid = (): DataGridElement | null => {\n if (cachedGrid) return cachedGrid;\n\n const grid = elementRef.nativeElement.querySelector('tbw-grid') as DataGridElement | null;\n if (grid) {\n cachedGrid = grid;\n // Start ready() check only once\n if (!readyPromiseStarted) {\n readyPromiseStarted = true;\n grid.ready?.().then(() => isReady.set(true));\n }\n }\n return grid;\n };\n\n const getPlugin = (): ExportPlugin | undefined => {\n return getGrid()?.getPlugin(ExportPlugin);\n };\n\n // Eagerly discover the grid after the first render so isReady updates\n // without requiring a programmatic method call. Falls back to a\n // MutationObserver for lazy-rendered tabs, *ngIf, @defer, etc.\n afterNextRender(() => {\n if (getGrid()) return;\n\n const host = elementRef.nativeElement as HTMLElement;\n const observer = new MutationObserver(() => {\n if (getGrid()) observer.disconnect();\n });\n observer.observe(host, { childList: true, subtree: true });\n\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n return {\n isReady: isReady.asReadonly(),\n\n exportToCsv: (filename?: string, params?: Partial<ExportParams>) => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:export] ExportPlugin not found.\\n\\n` +\n ` → Enable export on the grid:\\n` +\n ` <tbw-grid [export]=\"true\" />`,\n );\n return;\n }\n plugin.exportCsv({ ...params, fileName: filename ?? params?.fileName ?? 'export.csv' });\n },\n\n exportToExcel: (filename?: string, params?: Partial<ExportParams>) => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:export] ExportPlugin not found.\\n\\n` +\n ` → Enable export on the grid:\\n` +\n ` <tbw-grid [export]=\"true\" />`,\n );\n return;\n }\n plugin.exportExcel({ ...params, fileName: filename ?? params?.fileName ?? 'export.xlsx' });\n },\n\n exportToJson: (filename?: string, params?: Partial<ExportParams>) => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:export] ExportPlugin not found.\\n\\n` +\n ` → Enable export on the grid:\\n` +\n ` <tbw-grid [export]=\"true\" />`,\n );\n return;\n }\n plugin.exportJson({ ...params, fileName: filename ?? params?.fileName ?? 'export.json' });\n },\n\n isExporting: () => {\n return getPlugin()?.isExporting() ?? false;\n },\n\n getLastExport: () => {\n return getPlugin()?.getLastExport() ?? null;\n },\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -46,7 +46,7 @@ registerFeature('filtering', (config) => {
|
|
|
46
46
|
*
|
|
47
47
|
* @example
|
|
48
48
|
* ```typescript
|
|
49
|
-
* import { Component
|
|
49
|
+
* import { Component } from '@angular/core';
|
|
50
50
|
* import { Grid } from '@toolbox-web/grid-angular';
|
|
51
51
|
* import '@toolbox-web/grid-angular/features/filtering';
|
|
52
52
|
* import { injectGridFiltering } from '@toolbox-web/grid-angular/features/filtering';
|
|
@@ -54,7 +54,6 @@ registerFeature('filtering', (config) => {
|
|
|
54
54
|
* @Component({
|
|
55
55
|
* selector: 'app-my-grid',
|
|
56
56
|
* imports: [Grid],
|
|
57
|
-
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
58
57
|
* template: `
|
|
59
58
|
* <input (input)="applyFilter($event)" placeholder="Filter by name..." />
|
|
60
59
|
* <span>{{ filtering.getFilteredRowCount() }} results</span>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbox-web-grid-angular-features-filtering.mjs","sources":["../../../../libs/grid-angular/features/filtering/src/index.ts","../../../../libs/grid-angular/features/filtering/src/toolbox-web-grid-angular-features-filtering.ts"],"sourcesContent":["/**\n * Filtering feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `filtering` input on Grid directive.\n * Also exports `injectGridFiltering()` for programmatic filter control.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/filtering';\n *\n * <tbw-grid [filtering]=\"true\" />\n * <tbw-grid [filtering]=\"{ debounceMs: 200 }\" />\n * ```\n *\n * @example Using injectGridFiltering\n * ```typescript\n * import { injectGridFiltering } from '@toolbox-web/grid-angular/features/filtering';\n *\n * @Component({...})\n * export class MyComponent {\n * private filtering = injectGridFiltering();\n *\n * filterByStatus(status: string) {\n * this.filtering.setFilter('status', { operator: 'equals', value: status });\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { registerFeature } from '@toolbox-web/grid-angular';\nimport { FilteringPlugin, type FilterModel } from '@toolbox-web/grid/plugins/filtering';\n\nregisterFeature('filtering', (config) => {\n if (config === true) {\n return new FilteringPlugin();\n }\n return new FilteringPlugin(config ?? undefined);\n});\n\n/**\n * Filtering methods returned from injectGridFiltering.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n */\nexport interface FilteringMethods {\n /**\n * Set a filter on a specific field.\n * @param field - The field name to filter\n * @param filter - Filter configuration, or null to remove\n */\n setFilter: (field: string, filter: Omit<FilterModel, 'field'> | null) => void;\n\n /**\n * Get the current filter for a field.\n */\n getFilter: (field: string) => FilterModel | undefined;\n\n /**\n * Get all active filters.\n */\n getFilters: () => FilterModel[];\n\n /**\n * Set all filters at once (replaces existing).\n */\n setFilterModel: (filters: FilterModel[]) => void;\n\n /**\n * Clear all active filters.\n */\n clearAllFilters: () => void;\n\n /**\n * Clear filter for a specific field.\n */\n clearFieldFilter: (field: string) => void;\n\n /**\n * Check if a field has an active filter.\n */\n isFieldFiltered: (field: string) => boolean;\n\n /**\n * Get the count of rows after filtering.\n */\n getFilteredRowCount: () => number;\n\n /**\n * Get unique values for a field (for building filter dropdowns).\n */\n getUniqueValues: (field: string) => unknown[];\n\n /**\n * Signal indicating if grid is ready.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic filter control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization.\n *\n * @example\n * ```typescript\n * import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\n * import { Grid } from '@toolbox-web/grid-angular';\n * import '@toolbox-web/grid-angular/features/filtering';\n * import { injectGridFiltering } from '@toolbox-web/grid-angular/features/filtering';\n *\n * @Component({\n * selector: 'app-my-grid',\n * imports: [Grid],\n * schemas: [CUSTOM_ELEMENTS_SCHEMA],\n * template: `\n * <input (input)=\"applyFilter($event)\" placeholder=\"Filter by name...\" />\n * <span>{{ filtering.getFilteredRowCount() }} results</span>\n * <button (click)=\"filtering.clearAllFilters()\">Clear</button>\n * <tbw-grid [rows]=\"rows\" [filtering]=\"true\"></tbw-grid>\n * `\n * })\n * export class MyGridComponent {\n * filtering = injectGridFiltering();\n *\n * applyFilter(event: Event) {\n * const value = (event.target as HTMLInputElement).value;\n * this.filtering.setFilter('name', value ? { operator: 'contains', value } : null);\n * }\n * }\n * ```\n */\nexport function injectGridFiltering(): FilteringMethods {\n const elementRef = inject(ElementRef);\n const destroyRef = inject(DestroyRef);\n const isReady = signal(false);\n\n let cachedGrid: DataGridElement | null = null;\n let readyPromiseStarted = false;\n\n const getGrid = (): DataGridElement | null => {\n if (cachedGrid) return cachedGrid;\n\n const grid = elementRef.nativeElement.querySelector('tbw-grid') as DataGridElement | null;\n if (grid) {\n cachedGrid = grid;\n if (!readyPromiseStarted) {\n readyPromiseStarted = true;\n grid.ready?.().then(() => isReady.set(true));\n }\n }\n return grid;\n };\n\n const getPlugin = (): FilteringPlugin | undefined => {\n return getGrid()?.getPlugin(FilteringPlugin);\n };\n\n // Eagerly discover the grid after the first render so isReady updates\n // without requiring a programmatic method call. Falls back to a\n // MutationObserver for lazy-rendered tabs, *ngIf, @defer, etc.\n afterNextRender(() => {\n if (getGrid()) return;\n\n const host = elementRef.nativeElement as HTMLElement;\n const observer = new MutationObserver(() => {\n if (getGrid()) observer.disconnect();\n });\n observer.observe(host, { childList: true, subtree: true });\n\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n return {\n isReady: isReady.asReadonly(),\n\n setFilter: (field: string, filter: Omit<FilterModel, 'field'> | null) => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:filtering] FilteringPlugin not found.\\n\\n` +\n ` → Enable filtering on the grid:\\n` +\n ` <tbw-grid [filtering]=\"true\" />`,\n );\n return;\n }\n plugin.setFilter(field, filter);\n },\n\n getFilter: (field: string) => getPlugin()?.getFilter(field),\n\n getFilters: () => getPlugin()?.getFilters() ?? [],\n\n setFilterModel: (filters: FilterModel[]) => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:filtering] FilteringPlugin not found.\\n\\n` +\n ` → Enable filtering on the grid:\\n` +\n ` <tbw-grid [filtering]=\"true\" />`,\n );\n return;\n }\n plugin.setFilterModel(filters);\n },\n\n clearAllFilters: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:filtering] FilteringPlugin not found.\\n\\n` +\n ` → Enable filtering on the grid:\\n` +\n ` <tbw-grid [filtering]=\"true\" />`,\n );\n return;\n }\n plugin.clearAllFilters();\n },\n\n clearFieldFilter: (field: string) => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:filtering] FilteringPlugin not found.\\n\\n` +\n ` → Enable filtering on the grid:\\n` +\n ` <tbw-grid [filtering]=\"true\" />`,\n );\n return;\n }\n plugin.clearFieldFilter(field);\n },\n\n isFieldFiltered: (field: string) => getPlugin()?.isFieldFiltered(field) ?? false,\n\n getFilteredRowCount: () => getPlugin()?.getFilteredRowCount() ?? 0,\n\n getUniqueValues: (field: string) => getPlugin()?.getUniqueValues(field) ?? [],\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toolbox-web-grid-angular-features-filtering.mjs","sources":["../../../../libs/grid-angular/features/filtering/src/index.ts","../../../../libs/grid-angular/features/filtering/src/toolbox-web-grid-angular-features-filtering.ts"],"sourcesContent":["/**\n * Filtering feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `filtering` input on Grid directive.\n * Also exports `injectGridFiltering()` for programmatic filter control.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/filtering';\n *\n * <tbw-grid [filtering]=\"true\" />\n * <tbw-grid [filtering]=\"{ debounceMs: 200 }\" />\n * ```\n *\n * @example Using injectGridFiltering\n * ```typescript\n * import { injectGridFiltering } from '@toolbox-web/grid-angular/features/filtering';\n *\n * @Component({...})\n * export class MyComponent {\n * private filtering = injectGridFiltering();\n *\n * filterByStatus(status: string) {\n * this.filtering.setFilter('status', { operator: 'equals', value: status });\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { registerFeature } from '@toolbox-web/grid-angular';\nimport { FilteringPlugin, type FilterModel } from '@toolbox-web/grid/plugins/filtering';\n\nregisterFeature('filtering', (config) => {\n if (config === true) {\n return new FilteringPlugin();\n }\n return new FilteringPlugin(config ?? undefined);\n});\n\n/**\n * Filtering methods returned from injectGridFiltering.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n */\nexport interface FilteringMethods {\n /**\n * Set a filter on a specific field.\n * @param field - The field name to filter\n * @param filter - Filter configuration, or null to remove\n */\n setFilter: (field: string, filter: Omit<FilterModel, 'field'> | null) => void;\n\n /**\n * Get the current filter for a field.\n */\n getFilter: (field: string) => FilterModel | undefined;\n\n /**\n * Get all active filters.\n */\n getFilters: () => FilterModel[];\n\n /**\n * Set all filters at once (replaces existing).\n */\n setFilterModel: (filters: FilterModel[]) => void;\n\n /**\n * Clear all active filters.\n */\n clearAllFilters: () => void;\n\n /**\n * Clear filter for a specific field.\n */\n clearFieldFilter: (field: string) => void;\n\n /**\n * Check if a field has an active filter.\n */\n isFieldFiltered: (field: string) => boolean;\n\n /**\n * Get the count of rows after filtering.\n */\n getFilteredRowCount: () => number;\n\n /**\n * Get unique values for a field (for building filter dropdowns).\n */\n getUniqueValues: (field: string) => unknown[];\n\n /**\n * Signal indicating if grid is ready.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic filter control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization.\n *\n * @example\n * ```typescript\n * import { Component } from '@angular/core';\n * import { Grid } from '@toolbox-web/grid-angular';\n * import '@toolbox-web/grid-angular/features/filtering';\n * import { injectGridFiltering } from '@toolbox-web/grid-angular/features/filtering';\n *\n * @Component({\n * selector: 'app-my-grid',\n * imports: [Grid],\n * template: `\n * <input (input)=\"applyFilter($event)\" placeholder=\"Filter by name...\" />\n * <span>{{ filtering.getFilteredRowCount() }} results</span>\n * <button (click)=\"filtering.clearAllFilters()\">Clear</button>\n * <tbw-grid [rows]=\"rows\" [filtering]=\"true\"></tbw-grid>\n * `\n * })\n * export class MyGridComponent {\n * filtering = injectGridFiltering();\n *\n * applyFilter(event: Event) {\n * const value = (event.target as HTMLInputElement).value;\n * this.filtering.setFilter('name', value ? { operator: 'contains', value } : null);\n * }\n * }\n * ```\n */\nexport function injectGridFiltering(): FilteringMethods {\n const elementRef = inject(ElementRef);\n const destroyRef = inject(DestroyRef);\n const isReady = signal(false);\n\n let cachedGrid: DataGridElement | null = null;\n let readyPromiseStarted = false;\n\n const getGrid = (): DataGridElement | null => {\n if (cachedGrid) return cachedGrid;\n\n const grid = elementRef.nativeElement.querySelector('tbw-grid') as DataGridElement | null;\n if (grid) {\n cachedGrid = grid;\n if (!readyPromiseStarted) {\n readyPromiseStarted = true;\n grid.ready?.().then(() => isReady.set(true));\n }\n }\n return grid;\n };\n\n const getPlugin = (): FilteringPlugin | undefined => {\n return getGrid()?.getPlugin(FilteringPlugin);\n };\n\n // Eagerly discover the grid after the first render so isReady updates\n // without requiring a programmatic method call. Falls back to a\n // MutationObserver for lazy-rendered tabs, *ngIf, @defer, etc.\n afterNextRender(() => {\n if (getGrid()) return;\n\n const host = elementRef.nativeElement as HTMLElement;\n const observer = new MutationObserver(() => {\n if (getGrid()) observer.disconnect();\n });\n observer.observe(host, { childList: true, subtree: true });\n\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n return {\n isReady: isReady.asReadonly(),\n\n setFilter: (field: string, filter: Omit<FilterModel, 'field'> | null) => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:filtering] FilteringPlugin not found.\\n\\n` +\n ` → Enable filtering on the grid:\\n` +\n ` <tbw-grid [filtering]=\"true\" />`,\n );\n return;\n }\n plugin.setFilter(field, filter);\n },\n\n getFilter: (field: string) => getPlugin()?.getFilter(field),\n\n getFilters: () => getPlugin()?.getFilters() ?? [],\n\n setFilterModel: (filters: FilterModel[]) => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:filtering] FilteringPlugin not found.\\n\\n` +\n ` → Enable filtering on the grid:\\n` +\n ` <tbw-grid [filtering]=\"true\" />`,\n );\n return;\n }\n plugin.setFilterModel(filters);\n },\n\n clearAllFilters: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:filtering] FilteringPlugin not found.\\n\\n` +\n ` → Enable filtering on the grid:\\n` +\n ` <tbw-grid [filtering]=\"true\" />`,\n );\n return;\n }\n plugin.clearAllFilters();\n },\n\n clearFieldFilter: (field: string) => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:filtering] FilteringPlugin not found.\\n\\n` +\n ` → Enable filtering on the grid:\\n` +\n ` <tbw-grid [filtering]=\"true\" />`,\n );\n return;\n }\n plugin.clearFieldFilter(field);\n },\n\n isFieldFiltered: (field: string) => getPlugin()?.isFieldFiltered(field) ?? false,\n\n getFilteredRowCount: () => getPlugin()?.getFilteredRowCount() ?? 0,\n\n getUniqueValues: (field: string) => getPlugin()?.getUniqueValues(field) ?? [],\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -45,7 +45,7 @@ registerFeature('print', (config) => {
|
|
|
45
45
|
*
|
|
46
46
|
* @example
|
|
47
47
|
* ```typescript
|
|
48
|
-
* import { Component
|
|
48
|
+
* import { Component } from '@angular/core';
|
|
49
49
|
* import { Grid } from '@toolbox-web/grid-angular';
|
|
50
50
|
* import '@toolbox-web/grid-angular/features/print';
|
|
51
51
|
* import { injectGridPrint } from '@toolbox-web/grid-angular/features/print';
|
|
@@ -53,7 +53,6 @@ registerFeature('print', (config) => {
|
|
|
53
53
|
* @Component({
|
|
54
54
|
* selector: 'app-my-grid',
|
|
55
55
|
* imports: [Grid],
|
|
56
|
-
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
57
56
|
* template: `
|
|
58
57
|
* <button (click)="handlePrint()" [disabled]="gridPrint.isPrinting()">
|
|
59
58
|
* {{ gridPrint.isPrinting() ? 'Printing...' : 'Print' }}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbox-web-grid-angular-features-print.mjs","sources":["../../../../libs/grid-angular/features/print/src/index.ts","../../../../libs/grid-angular/features/print/src/toolbox-web-grid-angular-features-print.ts"],"sourcesContent":["/**\n * Print feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `print` input on Grid directive.\n * Also exports `injectGridPrint()` for programmatic print control.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/print';\n *\n * <tbw-grid [print]=\"true\" />\n * ```\n *\n * @example Using injectGridPrint\n * ```typescript\n * import { injectGridPrint } from '@toolbox-web/grid-angular/features/print';\n *\n * @Component({...})\n * export class MyComponent {\n * private gridPrint = injectGridPrint();\n *\n * printReport() {\n * this.gridPrint.print({ title: 'My Report' });\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { registerFeature } from '@toolbox-web/grid-angular';\nimport { PrintPlugin, type PrintParams } from '@toolbox-web/grid/plugins/print';\n\nregisterFeature('print', (config) => {\n if (config === true) {\n return new PrintPlugin();\n }\n return new PrintPlugin(config ?? undefined);\n});\n\n/**\n * Print methods returned from injectGridPrint.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n */\nexport interface PrintMethods {\n /**\n * Print the grid.\n * Opens browser print dialog after preparing the grid for printing.\n * @param params - Optional print parameters\n */\n print: (params?: PrintParams) => Promise<void>;\n\n /**\n * Check if a print operation is currently in progress.\n */\n isPrinting: () => boolean;\n\n /**\n * Signal indicating if grid is ready.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic print control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization.\n *\n * @example\n * ```typescript\n * import { Component
|
|
1
|
+
{"version":3,"file":"toolbox-web-grid-angular-features-print.mjs","sources":["../../../../libs/grid-angular/features/print/src/index.ts","../../../../libs/grid-angular/features/print/src/toolbox-web-grid-angular-features-print.ts"],"sourcesContent":["/**\n * Print feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `print` input on Grid directive.\n * Also exports `injectGridPrint()` for programmatic print control.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/print';\n *\n * <tbw-grid [print]=\"true\" />\n * ```\n *\n * @example Using injectGridPrint\n * ```typescript\n * import { injectGridPrint } from '@toolbox-web/grid-angular/features/print';\n *\n * @Component({...})\n * export class MyComponent {\n * private gridPrint = injectGridPrint();\n *\n * printReport() {\n * this.gridPrint.print({ title: 'My Report' });\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { registerFeature } from '@toolbox-web/grid-angular';\nimport { PrintPlugin, type PrintParams } from '@toolbox-web/grid/plugins/print';\n\nregisterFeature('print', (config) => {\n if (config === true) {\n return new PrintPlugin();\n }\n return new PrintPlugin(config ?? undefined);\n});\n\n/**\n * Print methods returned from injectGridPrint.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n */\nexport interface PrintMethods {\n /**\n * Print the grid.\n * Opens browser print dialog after preparing the grid for printing.\n * @param params - Optional print parameters\n */\n print: (params?: PrintParams) => Promise<void>;\n\n /**\n * Check if a print operation is currently in progress.\n */\n isPrinting: () => boolean;\n\n /**\n * Signal indicating if grid is ready.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic print control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization.\n *\n * @example\n * ```typescript\n * import { Component } from '@angular/core';\n * import { Grid } from '@toolbox-web/grid-angular';\n * import '@toolbox-web/grid-angular/features/print';\n * import { injectGridPrint } from '@toolbox-web/grid-angular/features/print';\n *\n * @Component({\n * selector: 'app-my-grid',\n * imports: [Grid],\n * template: `\n * <button (click)=\"handlePrint()\" [disabled]=\"gridPrint.isPrinting()\">\n * {{ gridPrint.isPrinting() ? 'Printing...' : 'Print' }}\n * </button>\n * <tbw-grid [rows]=\"rows\" [print]=\"true\"></tbw-grid>\n * `\n * })\n * export class MyGridComponent {\n * gridPrint = injectGridPrint();\n *\n * async handlePrint() {\n * await this.gridPrint.print({ title: 'Employee Report', isolate: true });\n * console.log('Print dialog closed');\n * }\n * }\n * ```\n */\nexport function injectGridPrint(): PrintMethods {\n const elementRef = inject(ElementRef);\n const destroyRef = inject(DestroyRef);\n const isReady = signal(false);\n\n let cachedGrid: DataGridElement | null = null;\n let readyPromiseStarted = false;\n\n const getGrid = (): DataGridElement | null => {\n if (cachedGrid) return cachedGrid;\n\n const grid = elementRef.nativeElement.querySelector('tbw-grid') as DataGridElement | null;\n if (grid) {\n cachedGrid = grid;\n if (!readyPromiseStarted) {\n readyPromiseStarted = true;\n grid.ready?.().then(() => isReady.set(true));\n }\n }\n return grid;\n };\n\n const getPlugin = (): PrintPlugin | undefined => {\n return getGrid()?.getPlugin(PrintPlugin);\n };\n\n // Eagerly discover the grid after the first render so isReady updates\n // without requiring a programmatic method call. Falls back to a\n // MutationObserver for lazy-rendered tabs, *ngIf, @defer, etc.\n afterNextRender(() => {\n if (getGrid()) return;\n\n const host = elementRef.nativeElement as HTMLElement;\n const observer = new MutationObserver(() => {\n if (getGrid()) observer.disconnect();\n });\n observer.observe(host, { childList: true, subtree: true });\n\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n return {\n isReady: isReady.asReadonly(),\n\n print: async (params?: PrintParams) => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:print] PrintPlugin not found.\\n\\n` +\n ` → Enable print on the grid:\\n` +\n ` <tbw-grid [print]=\"true\" />`,\n );\n return;\n }\n await plugin.print(params);\n },\n\n isPrinting: () => getPlugin()?.isPrinting() ?? false,\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BG;AAOH,eAAe,CAAC,OAAO,EAAE,CAAC,MAAM,KAAI;AAClC,IAAA,IAAI,MAAM,KAAK,IAAI,EAAE;QACnB,OAAO,IAAI,WAAW,EAAE;IAC1B;AACA,IAAA,OAAO,IAAI,WAAW,CAAC,MAAM,IAAI,SAAS,CAAC;AAC7C,CAAC,CAAC;AA0BF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCG;SACa,eAAe,GAAA;AAC7B,IAAA,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AACrC,IAAA,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AACrC,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,mDAAC;IAE7B,IAAI,UAAU,GAA2B,IAAI;IAC7C,IAAI,mBAAmB,GAAG,KAAK;IAE/B,MAAM,OAAO,GAAG,MAA6B;AAC3C,QAAA,IAAI,UAAU;AAAE,YAAA,OAAO,UAAU;QAEjC,MAAM,IAAI,GAAG,UAAU,CAAC,aAAa,CAAC,aAAa,CAAC,UAAU,CAA2B;QACzF,IAAI,IAAI,EAAE;YACR,UAAU,GAAG,IAAI;YACjB,IAAI,CAAC,mBAAmB,EAAE;gBACxB,mBAAmB,GAAG,IAAI;AAC1B,gBAAA,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9C;QACF;AACA,QAAA,OAAO,IAAI;AACb,IAAA,CAAC;IAED,MAAM,SAAS,GAAG,MAA8B;AAC9C,QAAA,OAAO,OAAO,EAAE,EAAE,SAAS,CAAC,WAAW,CAAC;AAC1C,IAAA,CAAC;;;;IAKD,eAAe,CAAC,MAAK;AACnB,QAAA,IAAI,OAAO,EAAE;YAAE;AAEf,QAAA,MAAM,IAAI,GAAG,UAAU,CAAC,aAA4B;AACpD,QAAA,MAAM,QAAQ,GAAG,IAAI,gBAAgB,CAAC,MAAK;AACzC,YAAA,IAAI,OAAO,EAAE;gBAAE,QAAQ,CAAC,UAAU,EAAE;AACtC,QAAA,CAAC,CAAC;AACF,QAAA,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAE1D,UAAU,CAAC,SAAS,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAC;AACnD,IAAA,CAAC,CAAC;IAEF,OAAO;AACL,QAAA,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE;AAE7B,QAAA,KAAK,EAAE,OAAO,MAAoB,KAAI;AACpC,YAAA,MAAM,MAAM,GAAG,SAAS,EAAE;YAC1B,IAAI,CAAC,MAAM,EAAE;gBACX,OAAO,CAAC,IAAI,CACV,CAAA,2CAAA,CAA6C;oBAC3C,CAAA,+BAAA,CAAiC;AACjC,oBAAA,CAAA,+BAAA,CAAiC,CACpC;gBACD;YACF;AACA,YAAA,MAAM,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;QAC5B,CAAC;QAED,UAAU,EAAE,MAAM,SAAS,EAAE,EAAE,UAAU,EAAE,IAAI,KAAK;KACrD;AACH;;AC9JA;;AAEG;;;;"}
|
|
@@ -54,7 +54,7 @@ registerFeature('selection', (config) => {
|
|
|
54
54
|
*
|
|
55
55
|
* @example
|
|
56
56
|
* ```typescript
|
|
57
|
-
* import { Component
|
|
57
|
+
* import { Component } from '@angular/core';
|
|
58
58
|
* import { Grid } from '@toolbox-web/grid-angular';
|
|
59
59
|
* import '@toolbox-web/grid-angular/features/selection';
|
|
60
60
|
* import { injectGridSelection } from '@toolbox-web/grid-angular/features/selection';
|
|
@@ -62,7 +62,6 @@ registerFeature('selection', (config) => {
|
|
|
62
62
|
* @Component({
|
|
63
63
|
* selector: 'app-my-grid',
|
|
64
64
|
* imports: [Grid],
|
|
65
|
-
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
66
65
|
* template: `
|
|
67
66
|
* <button (click)="handleSelectAll()">Select All</button>
|
|
68
67
|
* <tbw-grid [rows]="rows" [selection]="'range'"></tbw-grid>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbox-web-grid-angular-features-selection.mjs","sources":["../../../../libs/grid-angular/features/selection/src/index.ts","../../../../libs/grid-angular/features/selection/src/toolbox-web-grid-angular-features-selection.ts"],"sourcesContent":["/**\n * Selection feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `selection` input on Grid directive.\n * Also exports `injectGridSelection()` for programmatic selection control.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/selection';\n *\n * <tbw-grid [selection]=\"'range'\" />\n * ```\n *\n * @example Using injectGridSelection\n * ```typescript\n * import { injectGridSelection } from '@toolbox-web/grid-angular/features/selection';\n *\n * @Component({...})\n * export class MyComponent {\n * private selection = injectGridSelection<Employee>();\n *\n * selectAll() {\n * this.selection.selectAll();\n * }\n *\n * getSelected() {\n * return this.selection.getSelection();\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { registerFeature } from '@toolbox-web/grid-angular';\nimport {\n SelectionPlugin,\n type CellRange,\n type SelectionChangeDetail,\n type SelectionResult,\n} from '@toolbox-web/grid/plugins/selection';\n\nregisterFeature('selection', (config) => {\n // Handle shorthand: 'cell', 'row', 'range'\n if (config === 'cell' || config === 'row' || config === 'range') {\n return new SelectionPlugin({ mode: config });\n }\n // Full config object\n return new SelectionPlugin(config ?? undefined);\n});\n\n/**\n * Selection methods returned from injectGridSelection.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n * This ensures it works with lazy-rendered tabs, conditional rendering, etc.\n */\nexport interface SelectionMethods<TRow = unknown> {\n /**\n * Select all rows (row mode) or all cells (range mode).\n */\n selectAll: () => void;\n\n /**\n * Clear all selection.\n */\n clearSelection: () => void;\n\n /**\n * Get the current selection state (imperative, point-in-time snapshot).\n * For reactive selection state, use the `selection` signal instead.\n */\n getSelection: () => SelectionResult | null;\n\n /**\n * Check if a specific cell is selected.\n */\n isCellSelected: (row: number, col: number) => boolean;\n\n /**\n * Set selection ranges programmatically.\n */\n setRanges: (ranges: CellRange[]) => void;\n\n /**\n * Reactive selection state. Updates automatically whenever the selection changes.\n * Null when no SelectionPlugin is active or no selection has been made yet.\n *\n * @example\n * ```typescript\n * readonly selection = injectGridSelection();\n *\n * // In template:\n * // {{ selection.selection()?.ranges?.length ?? 0 }} cells selected\n *\n * // In computed:\n * readonly hasSelection = computed(() => (this.selection.selection()?.ranges?.length ?? 0) > 0);\n * ```\n */\n selection: Signal<SelectionResult | null>;\n\n /**\n * Reactive selected row indices (sorted ascending). Updates automatically.\n * Convenience signal for row-mode selection — returns `[]` in cell/range modes\n * or when nothing is selected.\n *\n * **Prefer `selectedRows`** for getting actual row objects — it handles\n * index-to-object resolution correctly regardless of sorting/filtering.\n *\n * @example\n * ```typescript\n * readonly selection = injectGridSelection();\n *\n * // In template:\n * // {{ selection.selectedRowIndices().length }} rows selected\n * ```\n */\n selectedRowIndices: Signal<number[]>;\n\n /**\n * Reactive selected row objects. Updates automatically whenever the selection changes.\n * Works in all selection modes (row, cell, range) — returns the actual row objects\n * from the grid's processed (sorted/filtered) rows.\n *\n * This is the recommended way to get selected rows. Unlike manual index mapping,\n * it correctly resolves rows even when the grid is sorted or filtered.\n *\n * @example\n * ```typescript\n * readonly selection = injectGridSelection<Employee>();\n *\n * // In template:\n * // {{ selection.selectedRows().length }} rows selected\n *\n * // In computed:\n * readonly hasSelection = computed(() => this.selection.selectedRows().length > 0);\n * ```\n */\n selectedRows: Signal<TRow[]>;\n\n /**\n * Signal indicating if grid is ready.\n * The grid is discovered lazily, so this updates when first method call succeeds.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic selection control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization. This ensures it works reliably with:\n * - Lazy-rendered tabs\n * - Conditional rendering (*ngIf)\n * - Dynamic component loading\n *\n * @example\n * ```typescript\n * import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\n * import { Grid } from '@toolbox-web/grid-angular';\n * import '@toolbox-web/grid-angular/features/selection';\n * import { injectGridSelection } from '@toolbox-web/grid-angular/features/selection';\n *\n * @Component({\n * selector: 'app-my-grid',\n * imports: [Grid],\n * schemas: [CUSTOM_ELEMENTS_SCHEMA],\n * template: `\n * <button (click)=\"handleSelectAll()\">Select All</button>\n * <tbw-grid [rows]=\"rows\" [selection]=\"'range'\"></tbw-grid>\n * `\n * })\n * export class MyGridComponent {\n * selection = injectGridSelection();\n *\n * handleSelectAll() {\n * this.selection.selectAll();\n * }\n *\n * getSelectedRows() {\n * const selection = this.selection.getSelection();\n * if (!selection) return [];\n * // Derive rows from selection.ranges as needed\n * }\n * }\n * ```\n */\nexport function injectGridSelection<TRow = unknown>(): SelectionMethods<TRow> {\n const elementRef = inject(ElementRef);\n const destroyRef = inject(DestroyRef);\n const isReady = signal(false);\n\n // Reactive selection state\n const selectionSignal = signal<SelectionResult | null>(null);\n const selectedRowIndicesSignal = signal<number[]>([]);\n const selectedRowsSignal = signal<TRow[]>([]);\n\n // Lazy discovery: cached grid reference\n let cachedGrid: DataGridElement<TRow> | null = null;\n let readyPromiseStarted = false;\n let listenerAttached = false;\n\n /**\n * Handle selection-change events from the grid.\n * Updates both reactive signals.\n */\n const onSelectionChange = (e: Event): void => {\n const detail = (e as CustomEvent<SelectionChangeDetail>).detail;\n const plugin = getPlugin();\n if (plugin) {\n selectionSignal.set(plugin.getSelection());\n selectedRowIndicesSignal.set(detail.mode === 'row' ? plugin.getSelectedRowIndices() : []);\n selectedRowsSignal.set(plugin.getSelectedRows<TRow>());\n }\n };\n\n /**\n * Attach the selection-change event listener to the grid element.\n * Called once when the grid is first discovered.\n */\n const attachListener = (grid: DataGridElement<TRow>): void => {\n if (listenerAttached) return;\n listenerAttached = true;\n\n grid.addEventListener('selection-change', onSelectionChange);\n\n destroyRef.onDestroy(() => {\n grid.removeEventListener('selection-change', onSelectionChange);\n });\n };\n\n /**\n * Lazily find the grid element. Called on each method invocation.\n * Caches the reference once found and triggers ready() check.\n */\n const getGrid = (): DataGridElement<TRow> | null => {\n if (cachedGrid) return cachedGrid;\n\n const grid = elementRef.nativeElement.querySelector('tbw-grid') as DataGridElement<TRow> | null;\n if (grid) {\n cachedGrid = grid;\n attachListener(grid);\n // Start ready() check only once\n if (!readyPromiseStarted) {\n readyPromiseStarted = true;\n grid.ready?.().then(() => isReady.set(true));\n }\n }\n return grid;\n };\n\n const getPlugin = (): SelectionPlugin | undefined => {\n return getGrid()?.getPlugin(SelectionPlugin);\n };\n\n /**\n * Sync reactive signals with the current plugin state.\n * Called once when the grid is first discovered and ready.\n */\n const syncSignals = (): void => {\n const plugin = getPlugin();\n if (plugin) {\n selectionSignal.set(plugin.getSelection());\n const mode = (plugin as any).config?.mode;\n selectedRowIndicesSignal.set(mode === 'row' ? plugin.getSelectedRowIndices() : []);\n selectedRowsSignal.set(plugin.getSelectedRows<TRow>());\n }\n };\n\n // Discover the grid after the first render so the selection-change\n // listener is attached without requiring a programmatic method call.\n // Uses a MutationObserver as fallback for lazy-rendered tabs, *ngIf,\n // @defer, etc. where the grid may not be in the DOM on first render.\n afterNextRender(() => {\n const grid = getGrid();\n if (grid) {\n grid.ready?.().then(syncSignals);\n return;\n }\n\n // Grid not in DOM yet — watch for it to appear.\n const host = elementRef.nativeElement as HTMLElement;\n const observer = new MutationObserver(() => {\n const discovered = getGrid();\n if (discovered) {\n observer.disconnect();\n discovered.ready?.().then(syncSignals);\n }\n });\n observer.observe(host, { childList: true, subtree: true });\n\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n return {\n isReady: isReady.asReadonly(),\n selection: selectionSignal.asReadonly(),\n selectedRowIndices: selectedRowIndicesSignal.asReadonly(),\n selectedRows: selectedRowsSignal.asReadonly(),\n\n selectAll: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:selection] SelectionPlugin not found.\\n\\n` +\n ` → Enable selection on the grid:\\n` +\n ` <tbw-grid [selection]=\"'range'\" />`,\n );\n return;\n }\n const grid = getGrid();\n // Cast to any to access protected config\n const mode = (plugin as any).config?.mode;\n\n if (mode === 'row') {\n const rowCount = grid?.rows?.length ?? 0;\n const allIndices = new Set<number>();\n for (let i = 0; i < rowCount; i++) allIndices.add(i);\n (plugin as any).selected = allIndices;\n (plugin as any).requestAfterRender?.();\n } else if (mode === 'range') {\n const rowCount = grid?.rows?.length ?? 0;\n const colCount = (grid as any)?._columns?.length ?? 0;\n if (rowCount > 0 && colCount > 0) {\n plugin.setRanges([{ from: { row: 0, col: 0 }, to: { row: rowCount - 1, col: colCount - 1 } }]);\n }\n }\n },\n\n clearSelection: () => {\n getPlugin()?.clearSelection();\n },\n\n getSelection: () => {\n return getPlugin()?.getSelection() ?? null;\n },\n\n isCellSelected: (row: number, col: number) => {\n return getPlugin()?.isCellSelected(row, col) ?? false;\n },\n\n setRanges: (ranges: CellRange[]) => {\n getPlugin()?.setRanges(ranges);\n },\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toolbox-web-grid-angular-features-selection.mjs","sources":["../../../../libs/grid-angular/features/selection/src/index.ts","../../../../libs/grid-angular/features/selection/src/toolbox-web-grid-angular-features-selection.ts"],"sourcesContent":["/**\n * Selection feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `selection` input on Grid directive.\n * Also exports `injectGridSelection()` for programmatic selection control.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/selection';\n *\n * <tbw-grid [selection]=\"'range'\" />\n * ```\n *\n * @example Using injectGridSelection\n * ```typescript\n * import { injectGridSelection } from '@toolbox-web/grid-angular/features/selection';\n *\n * @Component({...})\n * export class MyComponent {\n * private selection = injectGridSelection<Employee>();\n *\n * selectAll() {\n * this.selection.selectAll();\n * }\n *\n * getSelected() {\n * return this.selection.getSelection();\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { registerFeature } from '@toolbox-web/grid-angular';\nimport {\n SelectionPlugin,\n type CellRange,\n type SelectionChangeDetail,\n type SelectionResult,\n} from '@toolbox-web/grid/plugins/selection';\n\nregisterFeature('selection', (config) => {\n // Handle shorthand: 'cell', 'row', 'range'\n if (config === 'cell' || config === 'row' || config === 'range') {\n return new SelectionPlugin({ mode: config });\n }\n // Full config object\n return new SelectionPlugin(config ?? undefined);\n});\n\n/**\n * Selection methods returned from injectGridSelection.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n * This ensures it works with lazy-rendered tabs, conditional rendering, etc.\n */\nexport interface SelectionMethods<TRow = unknown> {\n /**\n * Select all rows (row mode) or all cells (range mode).\n */\n selectAll: () => void;\n\n /**\n * Clear all selection.\n */\n clearSelection: () => void;\n\n /**\n * Get the current selection state (imperative, point-in-time snapshot).\n * For reactive selection state, use the `selection` signal instead.\n */\n getSelection: () => SelectionResult | null;\n\n /**\n * Check if a specific cell is selected.\n */\n isCellSelected: (row: number, col: number) => boolean;\n\n /**\n * Set selection ranges programmatically.\n */\n setRanges: (ranges: CellRange[]) => void;\n\n /**\n * Reactive selection state. Updates automatically whenever the selection changes.\n * Null when no SelectionPlugin is active or no selection has been made yet.\n *\n * @example\n * ```typescript\n * readonly selection = injectGridSelection();\n *\n * // In template:\n * // {{ selection.selection()?.ranges?.length ?? 0 }} cells selected\n *\n * // In computed:\n * readonly hasSelection = computed(() => (this.selection.selection()?.ranges?.length ?? 0) > 0);\n * ```\n */\n selection: Signal<SelectionResult | null>;\n\n /**\n * Reactive selected row indices (sorted ascending). Updates automatically.\n * Convenience signal for row-mode selection — returns `[]` in cell/range modes\n * or when nothing is selected.\n *\n * **Prefer `selectedRows`** for getting actual row objects — it handles\n * index-to-object resolution correctly regardless of sorting/filtering.\n *\n * @example\n * ```typescript\n * readonly selection = injectGridSelection();\n *\n * // In template:\n * // {{ selection.selectedRowIndices().length }} rows selected\n * ```\n */\n selectedRowIndices: Signal<number[]>;\n\n /**\n * Reactive selected row objects. Updates automatically whenever the selection changes.\n * Works in all selection modes (row, cell, range) — returns the actual row objects\n * from the grid's processed (sorted/filtered) rows.\n *\n * This is the recommended way to get selected rows. Unlike manual index mapping,\n * it correctly resolves rows even when the grid is sorted or filtered.\n *\n * @example\n * ```typescript\n * readonly selection = injectGridSelection<Employee>();\n *\n * // In template:\n * // {{ selection.selectedRows().length }} rows selected\n *\n * // In computed:\n * readonly hasSelection = computed(() => this.selection.selectedRows().length > 0);\n * ```\n */\n selectedRows: Signal<TRow[]>;\n\n /**\n * Signal indicating if grid is ready.\n * The grid is discovered lazily, so this updates when first method call succeeds.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic selection control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization. This ensures it works reliably with:\n * - Lazy-rendered tabs\n * - Conditional rendering (*ngIf)\n * - Dynamic component loading\n *\n * @example\n * ```typescript\n * import { Component } from '@angular/core';\n * import { Grid } from '@toolbox-web/grid-angular';\n * import '@toolbox-web/grid-angular/features/selection';\n * import { injectGridSelection } from '@toolbox-web/grid-angular/features/selection';\n *\n * @Component({\n * selector: 'app-my-grid',\n * imports: [Grid],\n * template: `\n * <button (click)=\"handleSelectAll()\">Select All</button>\n * <tbw-grid [rows]=\"rows\" [selection]=\"'range'\"></tbw-grid>\n * `\n * })\n * export class MyGridComponent {\n * selection = injectGridSelection();\n *\n * handleSelectAll() {\n * this.selection.selectAll();\n * }\n *\n * getSelectedRows() {\n * const selection = this.selection.getSelection();\n * if (!selection) return [];\n * // Derive rows from selection.ranges as needed\n * }\n * }\n * ```\n */\nexport function injectGridSelection<TRow = unknown>(): SelectionMethods<TRow> {\n const elementRef = inject(ElementRef);\n const destroyRef = inject(DestroyRef);\n const isReady = signal(false);\n\n // Reactive selection state\n const selectionSignal = signal<SelectionResult | null>(null);\n const selectedRowIndicesSignal = signal<number[]>([]);\n const selectedRowsSignal = signal<TRow[]>([]);\n\n // Lazy discovery: cached grid reference\n let cachedGrid: DataGridElement<TRow> | null = null;\n let readyPromiseStarted = false;\n let listenerAttached = false;\n\n /**\n * Handle selection-change events from the grid.\n * Updates both reactive signals.\n */\n const onSelectionChange = (e: Event): void => {\n const detail = (e as CustomEvent<SelectionChangeDetail>).detail;\n const plugin = getPlugin();\n if (plugin) {\n selectionSignal.set(plugin.getSelection());\n selectedRowIndicesSignal.set(detail.mode === 'row' ? plugin.getSelectedRowIndices() : []);\n selectedRowsSignal.set(plugin.getSelectedRows<TRow>());\n }\n };\n\n /**\n * Attach the selection-change event listener to the grid element.\n * Called once when the grid is first discovered.\n */\n const attachListener = (grid: DataGridElement<TRow>): void => {\n if (listenerAttached) return;\n listenerAttached = true;\n\n grid.addEventListener('selection-change', onSelectionChange);\n\n destroyRef.onDestroy(() => {\n grid.removeEventListener('selection-change', onSelectionChange);\n });\n };\n\n /**\n * Lazily find the grid element. Called on each method invocation.\n * Caches the reference once found and triggers ready() check.\n */\n const getGrid = (): DataGridElement<TRow> | null => {\n if (cachedGrid) return cachedGrid;\n\n const grid = elementRef.nativeElement.querySelector('tbw-grid') as DataGridElement<TRow> | null;\n if (grid) {\n cachedGrid = grid;\n attachListener(grid);\n // Start ready() check only once\n if (!readyPromiseStarted) {\n readyPromiseStarted = true;\n grid.ready?.().then(() => isReady.set(true));\n }\n }\n return grid;\n };\n\n const getPlugin = (): SelectionPlugin | undefined => {\n return getGrid()?.getPlugin(SelectionPlugin);\n };\n\n /**\n * Sync reactive signals with the current plugin state.\n * Called once when the grid is first discovered and ready.\n */\n const syncSignals = (): void => {\n const plugin = getPlugin();\n if (plugin) {\n selectionSignal.set(plugin.getSelection());\n const mode = (plugin as any).config?.mode;\n selectedRowIndicesSignal.set(mode === 'row' ? plugin.getSelectedRowIndices() : []);\n selectedRowsSignal.set(plugin.getSelectedRows<TRow>());\n }\n };\n\n // Discover the grid after the first render so the selection-change\n // listener is attached without requiring a programmatic method call.\n // Uses a MutationObserver as fallback for lazy-rendered tabs, *ngIf,\n // @defer, etc. where the grid may not be in the DOM on first render.\n afterNextRender(() => {\n const grid = getGrid();\n if (grid) {\n grid.ready?.().then(syncSignals);\n return;\n }\n\n // Grid not in DOM yet — watch for it to appear.\n const host = elementRef.nativeElement as HTMLElement;\n const observer = new MutationObserver(() => {\n const discovered = getGrid();\n if (discovered) {\n observer.disconnect();\n discovered.ready?.().then(syncSignals);\n }\n });\n observer.observe(host, { childList: true, subtree: true });\n\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n return {\n isReady: isReady.asReadonly(),\n selection: selectionSignal.asReadonly(),\n selectedRowIndices: selectedRowIndicesSignal.asReadonly(),\n selectedRows: selectedRowsSignal.asReadonly(),\n\n selectAll: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:selection] SelectionPlugin not found.\\n\\n` +\n ` → Enable selection on the grid:\\n` +\n ` <tbw-grid [selection]=\"'range'\" />`,\n );\n return;\n }\n const grid = getGrid();\n // Cast to any to access protected config\n const mode = (plugin as any).config?.mode;\n\n if (mode === 'row') {\n const rowCount = grid?.rows?.length ?? 0;\n const allIndices = new Set<number>();\n for (let i = 0; i < rowCount; i++) allIndices.add(i);\n (plugin as any).selected = allIndices;\n (plugin as any).requestAfterRender?.();\n } else if (mode === 'range') {\n const rowCount = grid?.rows?.length ?? 0;\n const colCount = (grid as any)?._columns?.length ?? 0;\n if (rowCount > 0 && colCount > 0) {\n plugin.setRanges([{ from: { row: 0, col: 0 }, to: { row: rowCount - 1, col: colCount - 1 } }]);\n }\n }\n },\n\n clearSelection: () => {\n getPlugin()?.clearSelection();\n },\n\n getSelection: () => {\n return getPlugin()?.getSelection() ?? null;\n },\n\n isCellSelected: (row: number, col: number) => {\n return getPlugin()?.isCellSelected(row, col) ?? false;\n },\n\n setRanges: (ranges: CellRange[]) => {\n getPlugin()?.setRanges(ranges);\n },\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -46,7 +46,7 @@ registerFeature('undoRedo', (config) => {
|
|
|
46
46
|
*
|
|
47
47
|
* @example
|
|
48
48
|
* ```typescript
|
|
49
|
-
* import { Component
|
|
49
|
+
* import { Component } from '@angular/core';
|
|
50
50
|
* import { Grid } from '@toolbox-web/grid-angular';
|
|
51
51
|
* import '@toolbox-web/grid-angular/features/editing';
|
|
52
52
|
* import '@toolbox-web/grid-angular/features/undo-redo';
|
|
@@ -55,7 +55,6 @@ registerFeature('undoRedo', (config) => {
|
|
|
55
55
|
* @Component({
|
|
56
56
|
* selector: 'app-my-grid',
|
|
57
57
|
* imports: [Grid],
|
|
58
|
-
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
59
58
|
* template: `
|
|
60
59
|
* <button (click)="undoRedo.undo()" [disabled]="!undoRedo.canUndo()">Undo</button>
|
|
61
60
|
* <button (click)="undoRedo.redo()" [disabled]="!undoRedo.canRedo()">Redo</button>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbox-web-grid-angular-features-undo-redo.mjs","sources":["../../../../libs/grid-angular/features/undo-redo/src/index.ts","../../../../libs/grid-angular/features/undo-redo/src/toolbox-web-grid-angular-features-undo-redo.ts"],"sourcesContent":["/**\n * Undo/Redo feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `undoRedo` input on Grid directive.\n * Also exports `injectGridUndoRedo()` for programmatic undo/redo control.\n * Requires editing feature to be enabled.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/editing';\n * import '@toolbox-web/grid-angular/features/undo-redo';\n *\n * <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />\n * ```\n *\n * @example Using injectGridUndoRedo\n * ```typescript\n * import { injectGridUndoRedo } from '@toolbox-web/grid-angular/features/undo-redo';\n *\n * @Component({...})\n * export class MyComponent {\n * private undoRedo = injectGridUndoRedo();\n *\n * undo() { this.undoRedo.undo(); }\n * redo() { this.undoRedo.redo(); }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { registerFeature } from '@toolbox-web/grid-angular';\nimport { UndoRedoPlugin, type EditAction } from '@toolbox-web/grid/plugins/undo-redo';\n\nregisterFeature('undoRedo', (config) => {\n if (config === true) {\n return new UndoRedoPlugin();\n }\n return new UndoRedoPlugin(config ?? undefined);\n});\n\n/**\n * Undo/Redo methods returned from injectGridUndoRedo.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n */\nexport interface UndoRedoMethods {\n /**\n * Undo the last edit action.\n * @returns The undone action, or null if nothing to undo\n */\n undo: () => EditAction | null;\n\n /**\n * Redo the last undone action.\n * @returns The redone action, or null if nothing to redo\n */\n redo: () => EditAction | null;\n\n /**\n * Reactive signal indicating whether undo is available.\n * Updates automatically when edits are made, undone, redone, or history is cleared.\n *\n * @example\n * ```typescript\n * // In template:\n * // <button [disabled]=\"!undoRedo.canUndo()\">Undo</button>\n *\n * // In computed:\n * readonly undoAvailable = computed(() => this.undoRedo.canUndo());\n * ```\n */\n canUndo: Signal<boolean>;\n\n /**\n * Reactive signal indicating whether redo is available.\n * Updates automatically when edits are made, undone, redone, or history is cleared.\n *\n * @example\n * ```typescript\n * // In template:\n * // <button [disabled]=\"!undoRedo.canRedo()\">Redo</button>\n *\n * // In computed:\n * readonly redoAvailable = computed(() => this.undoRedo.canRedo());\n * ```\n */\n canRedo: Signal<boolean>;\n\n /**\n * Clear all undo/redo history.\n */\n clearHistory: () => void;\n\n /**\n * Get a copy of the current undo stack.\n */\n getUndoStack: () => EditAction[];\n\n /**\n * Get a copy of the current redo stack.\n */\n getRedoStack: () => EditAction[];\n\n /**\n * Signal indicating if grid is ready.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic undo/redo control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization.\n *\n * @example\n * ```typescript\n * import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\n * import { Grid } from '@toolbox-web/grid-angular';\n * import '@toolbox-web/grid-angular/features/editing';\n * import '@toolbox-web/grid-angular/features/undo-redo';\n * import { injectGridUndoRedo } from '@toolbox-web/grid-angular/features/undo-redo';\n *\n * @Component({\n * selector: 'app-my-grid',\n * imports: [Grid],\n * schemas: [CUSTOM_ELEMENTS_SCHEMA],\n * template: `\n * <button (click)=\"undoRedo.undo()\" [disabled]=\"!undoRedo.canUndo()\">Undo</button>\n * <button (click)=\"undoRedo.redo()\" [disabled]=\"!undoRedo.canRedo()\">Redo</button>\n * <tbw-grid [rows]=\"rows\" [editing]=\"'dblclick'\" [undoRedo]=\"true\"></tbw-grid>\n * `\n * })\n * export class MyGridComponent {\n * undoRedo = injectGridUndoRedo();\n * }\n * ```\n */\nexport function injectGridUndoRedo(): UndoRedoMethods {\n const elementRef = inject(ElementRef);\n const destroyRef = inject(DestroyRef);\n const isReady = signal(false);\n\n // Reactive undo/redo availability signals\n const canUndoSignal = signal(false);\n const canRedoSignal = signal(false);\n\n let cachedGrid: DataGridElement | null = null;\n let readyPromiseStarted = false;\n let listenerAttached = false;\n\n /**\n * Sync canUndo/canRedo signals with the current plugin state.\n */\n const syncSignals = (): void => {\n const plugin = getPlugin();\n if (plugin) {\n canUndoSignal.set(plugin.canUndo());\n canRedoSignal.set(plugin.canRedo());\n }\n };\n\n /**\n * Attach event listeners to the grid for undo/redo state changes.\n * Listens for `undo`, `redo`, and `cell-commit` DOM events.\n */\n const attachListeners = (grid: DataGridElement): void => {\n if (listenerAttached) return;\n listenerAttached = true;\n\n grid.addEventListener('undo', syncSignals);\n grid.addEventListener('redo', syncSignals);\n grid.addEventListener('cell-commit', syncSignals);\n\n destroyRef.onDestroy(() => {\n grid.removeEventListener('undo', syncSignals);\n grid.removeEventListener('redo', syncSignals);\n grid.removeEventListener('cell-commit', syncSignals);\n });\n };\n\n const getGrid = (): DataGridElement | null => {\n if (cachedGrid) return cachedGrid;\n\n const grid = elementRef.nativeElement.querySelector('tbw-grid') as DataGridElement | null;\n if (grid) {\n cachedGrid = grid;\n attachListeners(grid);\n if (!readyPromiseStarted) {\n readyPromiseStarted = true;\n grid.ready?.().then(() => isReady.set(true));\n }\n }\n return grid;\n };\n\n const getPlugin = (): UndoRedoPlugin | undefined => {\n return getGrid()?.getPlugin(UndoRedoPlugin);\n };\n\n // Eagerly discover the grid after the first render so event listeners\n // are attached and isReady updates without requiring a programmatic\n // method call. Falls back to MutationObserver for lazy-rendered content.\n afterNextRender(() => {\n const grid = getGrid();\n if (grid) {\n grid.ready?.().then(syncSignals);\n return;\n }\n\n const host = elementRef.nativeElement as HTMLElement;\n const observer = new MutationObserver(() => {\n const discovered = getGrid();\n if (discovered) {\n observer.disconnect();\n discovered.ready?.().then(syncSignals);\n }\n });\n observer.observe(host, { childList: true, subtree: true });\n\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n return {\n isReady: isReady.asReadonly(),\n canUndo: canUndoSignal.asReadonly(),\n canRedo: canRedoSignal.asReadonly(),\n\n undo: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return null;\n }\n const result = plugin.undo();\n syncSignals();\n return result;\n },\n\n redo: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return null;\n }\n const result = plugin.redo();\n syncSignals();\n return result;\n },\n\n clearHistory: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return;\n }\n plugin.clearHistory();\n syncSignals();\n },\n\n getUndoStack: () => getPlugin()?.getUndoStack() ?? [],\n\n getRedoStack: () => getPlugin()?.getRedoStack() ?? [],\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toolbox-web-grid-angular-features-undo-redo.mjs","sources":["../../../../libs/grid-angular/features/undo-redo/src/index.ts","../../../../libs/grid-angular/features/undo-redo/src/toolbox-web-grid-angular-features-undo-redo.ts"],"sourcesContent":["/**\n * Undo/Redo feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `undoRedo` input on Grid directive.\n * Also exports `injectGridUndoRedo()` for programmatic undo/redo control.\n * Requires editing feature to be enabled.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/editing';\n * import '@toolbox-web/grid-angular/features/undo-redo';\n *\n * <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />\n * ```\n *\n * @example Using injectGridUndoRedo\n * ```typescript\n * import { injectGridUndoRedo } from '@toolbox-web/grid-angular/features/undo-redo';\n *\n * @Component({...})\n * export class MyComponent {\n * private undoRedo = injectGridUndoRedo();\n *\n * undo() { this.undoRedo.undo(); }\n * redo() { this.undoRedo.redo(); }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { registerFeature } from '@toolbox-web/grid-angular';\nimport { UndoRedoPlugin, type EditAction } from '@toolbox-web/grid/plugins/undo-redo';\n\nregisterFeature('undoRedo', (config) => {\n if (config === true) {\n return new UndoRedoPlugin();\n }\n return new UndoRedoPlugin(config ?? undefined);\n});\n\n/**\n * Undo/Redo methods returned from injectGridUndoRedo.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n */\nexport interface UndoRedoMethods {\n /**\n * Undo the last edit action.\n * @returns The undone action, or null if nothing to undo\n */\n undo: () => EditAction | null;\n\n /**\n * Redo the last undone action.\n * @returns The redone action, or null if nothing to redo\n */\n redo: () => EditAction | null;\n\n /**\n * Reactive signal indicating whether undo is available.\n * Updates automatically when edits are made, undone, redone, or history is cleared.\n *\n * @example\n * ```typescript\n * // In template:\n * // <button [disabled]=\"!undoRedo.canUndo()\">Undo</button>\n *\n * // In computed:\n * readonly undoAvailable = computed(() => this.undoRedo.canUndo());\n * ```\n */\n canUndo: Signal<boolean>;\n\n /**\n * Reactive signal indicating whether redo is available.\n * Updates automatically when edits are made, undone, redone, or history is cleared.\n *\n * @example\n * ```typescript\n * // In template:\n * // <button [disabled]=\"!undoRedo.canRedo()\">Redo</button>\n *\n * // In computed:\n * readonly redoAvailable = computed(() => this.undoRedo.canRedo());\n * ```\n */\n canRedo: Signal<boolean>;\n\n /**\n * Clear all undo/redo history.\n */\n clearHistory: () => void;\n\n /**\n * Get a copy of the current undo stack.\n */\n getUndoStack: () => EditAction[];\n\n /**\n * Get a copy of the current redo stack.\n */\n getRedoStack: () => EditAction[];\n\n /**\n * Signal indicating if grid is ready.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic undo/redo control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization.\n *\n * @example\n * ```typescript\n * import { Component } from '@angular/core';\n * import { Grid } from '@toolbox-web/grid-angular';\n * import '@toolbox-web/grid-angular/features/editing';\n * import '@toolbox-web/grid-angular/features/undo-redo';\n * import { injectGridUndoRedo } from '@toolbox-web/grid-angular/features/undo-redo';\n *\n * @Component({\n * selector: 'app-my-grid',\n * imports: [Grid],\n * template: `\n * <button (click)=\"undoRedo.undo()\" [disabled]=\"!undoRedo.canUndo()\">Undo</button>\n * <button (click)=\"undoRedo.redo()\" [disabled]=\"!undoRedo.canRedo()\">Redo</button>\n * <tbw-grid [rows]=\"rows\" [editing]=\"'dblclick'\" [undoRedo]=\"true\"></tbw-grid>\n * `\n * })\n * export class MyGridComponent {\n * undoRedo = injectGridUndoRedo();\n * }\n * ```\n */\nexport function injectGridUndoRedo(): UndoRedoMethods {\n const elementRef = inject(ElementRef);\n const destroyRef = inject(DestroyRef);\n const isReady = signal(false);\n\n // Reactive undo/redo availability signals\n const canUndoSignal = signal(false);\n const canRedoSignal = signal(false);\n\n let cachedGrid: DataGridElement | null = null;\n let readyPromiseStarted = false;\n let listenerAttached = false;\n\n /**\n * Sync canUndo/canRedo signals with the current plugin state.\n */\n const syncSignals = (): void => {\n const plugin = getPlugin();\n if (plugin) {\n canUndoSignal.set(plugin.canUndo());\n canRedoSignal.set(plugin.canRedo());\n }\n };\n\n /**\n * Attach event listeners to the grid for undo/redo state changes.\n * Listens for `undo`, `redo`, and `cell-commit` DOM events.\n */\n const attachListeners = (grid: DataGridElement): void => {\n if (listenerAttached) return;\n listenerAttached = true;\n\n grid.addEventListener('undo', syncSignals);\n grid.addEventListener('redo', syncSignals);\n grid.addEventListener('cell-commit', syncSignals);\n\n destroyRef.onDestroy(() => {\n grid.removeEventListener('undo', syncSignals);\n grid.removeEventListener('redo', syncSignals);\n grid.removeEventListener('cell-commit', syncSignals);\n });\n };\n\n const getGrid = (): DataGridElement | null => {\n if (cachedGrid) return cachedGrid;\n\n const grid = elementRef.nativeElement.querySelector('tbw-grid') as DataGridElement | null;\n if (grid) {\n cachedGrid = grid;\n attachListeners(grid);\n if (!readyPromiseStarted) {\n readyPromiseStarted = true;\n grid.ready?.().then(() => isReady.set(true));\n }\n }\n return grid;\n };\n\n const getPlugin = (): UndoRedoPlugin | undefined => {\n return getGrid()?.getPlugin(UndoRedoPlugin);\n };\n\n // Eagerly discover the grid after the first render so event listeners\n // are attached and isReady updates without requiring a programmatic\n // method call. Falls back to MutationObserver for lazy-rendered content.\n afterNextRender(() => {\n const grid = getGrid();\n if (grid) {\n grid.ready?.().then(syncSignals);\n return;\n }\n\n const host = elementRef.nativeElement as HTMLElement;\n const observer = new MutationObserver(() => {\n const discovered = getGrid();\n if (discovered) {\n observer.disconnect();\n discovered.ready?.().then(syncSignals);\n }\n });\n observer.observe(host, { childList: true, subtree: true });\n\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n return {\n isReady: isReady.asReadonly(),\n canUndo: canUndoSignal.asReadonly(),\n canRedo: canRedoSignal.asReadonly(),\n\n undo: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return null;\n }\n const result = plugin.undo();\n syncSignals();\n return result;\n },\n\n redo: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return null;\n }\n const result = plugin.redo();\n syncSignals();\n return result;\n },\n\n clearHistory: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return;\n }\n plugin.clearHistory();\n syncSignals();\n },\n\n getUndoStack: () => getPlugin()?.getUndoStack() ?? [],\n\n getRedoStack: () => getPlugin()?.getRedoStack() ?? [],\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -2310,13 +2310,12 @@ function provideGridIcons(icons) {
|
|
|
2310
2310
|
* ## Usage
|
|
2311
2311
|
*
|
|
2312
2312
|
* ```typescript
|
|
2313
|
-
* import { Component
|
|
2313
|
+
* import { Component } from '@angular/core';
|
|
2314
2314
|
* import { Grid, injectGrid } from '@toolbox-web/grid-angular';
|
|
2315
2315
|
*
|
|
2316
2316
|
* @Component({
|
|
2317
2317
|
* selector: 'app-my-grid',
|
|
2318
2318
|
* imports: [Grid],
|
|
2319
|
-
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
2320
2319
|
* template: `
|
|
2321
2320
|
* <button (click)="handleResize()">Force Layout</button>
|
|
2322
2321
|
* <button (click)="handleExport()" [disabled]="!grid.isReady()">Export</button>
|
|
@@ -2872,9 +2871,14 @@ class BaseGridEditor {
|
|
|
2872
2871
|
const grid = this.elementRef.nativeElement.closest('tbw-grid');
|
|
2873
2872
|
if (!grid)
|
|
2874
2873
|
return;
|
|
2874
|
+
const beforeHandler = () => this.onBeforeEditClose();
|
|
2875
|
+
grid.addEventListener('before-edit-close', beforeHandler, { once: true });
|
|
2875
2876
|
const handler = () => this.onEditClose();
|
|
2876
2877
|
grid.addEventListener('edit-close', handler, { once: true });
|
|
2877
|
-
this._editCloseCleanup = () =>
|
|
2878
|
+
this._editCloseCleanup = () => {
|
|
2879
|
+
grid.removeEventListener('before-edit-close', beforeHandler);
|
|
2880
|
+
grid.removeEventListener('edit-close', handler);
|
|
2881
|
+
};
|
|
2878
2882
|
}
|
|
2879
2883
|
// ============================================================================
|
|
2880
2884
|
// Methods
|
|
@@ -2891,6 +2895,18 @@ class BaseGridEditor {
|
|
|
2891
2895
|
isCellFocused() {
|
|
2892
2896
|
return this.elementRef.nativeElement.closest('[part="cell"]')?.classList.contains('cell-focus') ?? false;
|
|
2893
2897
|
}
|
|
2898
|
+
/**
|
|
2899
|
+
* Called **before** the grid clears editing state and destroys editor DOM.
|
|
2900
|
+
*
|
|
2901
|
+
* At this point the commit callback is still active, so subclasses can
|
|
2902
|
+
* call {@link commitValue} to flush any pending/deferred values.
|
|
2903
|
+
*
|
|
2904
|
+
* This fires only on the **commit** path (not on revert/cancel).
|
|
2905
|
+
* Use {@link onEditClose} for cleanup that should happen on both paths.
|
|
2906
|
+
*/
|
|
2907
|
+
onBeforeEditClose() {
|
|
2908
|
+
// Default: no-op. Subclasses override to flush pending values.
|
|
2909
|
+
}
|
|
2894
2910
|
/**
|
|
2895
2911
|
* Called when the grid ends the editing session for this cell.
|
|
2896
2912
|
*
|
|
@@ -3570,11 +3586,17 @@ class BaseOverlayEditor extends BaseGridEditor {
|
|
|
3570
3586
|
* `cell-focus` class changes. This handles row-editing mode where
|
|
3571
3587
|
* all editors exist simultaneously but only the focused cell's
|
|
3572
3588
|
* editor should have its overlay visible.
|
|
3589
|
+
*
|
|
3590
|
+
* A `justOpened` flash guard suppresses the observer from
|
|
3591
|
+
* immediately closing the overlay when `beginBulkEdit()` moves
|
|
3592
|
+
* focus to the first editable column. Without this guard,
|
|
3593
|
+
* double-click triggers a "flash open then close" effect.
|
|
3573
3594
|
*/
|
|
3574
3595
|
_setupFocusObserver() {
|
|
3575
3596
|
const cell = this._getCell();
|
|
3576
3597
|
if (!cell)
|
|
3577
3598
|
return;
|
|
3599
|
+
let justOpened = false;
|
|
3578
3600
|
this._focusObserver = new MutationObserver((mutations) => {
|
|
3579
3601
|
for (const mutation of mutations) {
|
|
3580
3602
|
if (mutation.type !== 'attributes' || mutation.attributeName !== 'class')
|
|
@@ -3582,10 +3604,17 @@ class BaseOverlayEditor extends BaseGridEditor {
|
|
|
3582
3604
|
const isFocused = cell.classList.contains('cell-focus');
|
|
3583
3605
|
if (isFocused && !this._isOpen) {
|
|
3584
3606
|
// Cell just gained focus — open overlay if appropriate
|
|
3607
|
+
justOpened = true;
|
|
3585
3608
|
this.showOverlay();
|
|
3586
3609
|
this.onOverlayOpened();
|
|
3610
|
+
// Clear the guard after a macrotask so that an immediate
|
|
3611
|
+
// focus-away (e.g. beginBulkEdit focus adjustment) does
|
|
3612
|
+
// not close the overlay in the same event loop tick.
|
|
3613
|
+
setTimeout(() => {
|
|
3614
|
+
justOpened = false;
|
|
3615
|
+
}, 0);
|
|
3587
3616
|
}
|
|
3588
|
-
else if (!isFocused && this._isOpen) {
|
|
3617
|
+
else if (!isFocused && this._isOpen && !justOpened) {
|
|
3589
3618
|
// Cell lost focus — hide overlay silently
|
|
3590
3619
|
this.hideOverlay(true);
|
|
3591
3620
|
}
|