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