@toolbox-web/grid-angular 0.9.1 → 0.10.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.
|
@@ -1,6 +1,8 @@
|
|
|
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 } from 'rxjs/operators';
|
|
4
6
|
import { DataGridElement } from '@toolbox-web/grid';
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -339,10 +341,12 @@ function getFormArrayContext(gridElement) {
|
|
|
339
341
|
* - Automatically syncs FormArray changes to the grid
|
|
340
342
|
*/
|
|
341
343
|
class GridFormArray {
|
|
344
|
+
destroyRef = inject(DestroyRef);
|
|
342
345
|
elementRef = inject((ElementRef));
|
|
343
346
|
cellCommitListener = null;
|
|
344
347
|
rowCommitListener = null;
|
|
345
348
|
touchListener = null;
|
|
349
|
+
valueChangesSubscription = null;
|
|
346
350
|
/**
|
|
347
351
|
* The FormArray to bind to the grid.
|
|
348
352
|
*/
|
|
@@ -359,15 +363,24 @@ class GridFormArray {
|
|
|
359
363
|
*/
|
|
360
364
|
syncValidation = input(true, ...(ngDevMode ? [{ debugName: "syncValidation" }] : []));
|
|
361
365
|
/**
|
|
362
|
-
* Effect that
|
|
366
|
+
* Effect that sets up valueChanges subscription when FormArray changes.
|
|
367
|
+
* This handles both initial binding and when the FormArray reference changes.
|
|
363
368
|
*/
|
|
364
369
|
syncFormArrayToGrid = effect(() => {
|
|
365
370
|
const formArray = this.formArray();
|
|
366
371
|
const grid = this.elementRef.nativeElement;
|
|
367
|
-
if (grid
|
|
368
|
-
|
|
372
|
+
if (!grid || !formArray)
|
|
373
|
+
return;
|
|
374
|
+
// Unsubscribe from previous FormArray if any
|
|
375
|
+
this.valueChangesSubscription?.unsubscribe();
|
|
376
|
+
// Subscribe to valueChanges to sync grid rows when FormArray content changes.
|
|
377
|
+
// Use startWith to immediately sync the current value.
|
|
378
|
+
// Note: We use getRawValue() to include disabled controls.
|
|
379
|
+
this.valueChangesSubscription = formArray.valueChanges
|
|
380
|
+
.pipe(startWith(formArray.getRawValue()), takeUntilDestroyed(this.destroyRef))
|
|
381
|
+
.subscribe(() => {
|
|
369
382
|
grid.rows = formArray.getRawValue();
|
|
370
|
-
}
|
|
383
|
+
});
|
|
371
384
|
}, ...(ngDevMode ? [{ debugName: "syncFormArrayToGrid" }] : []));
|
|
372
385
|
ngOnInit() {
|
|
373
386
|
const grid = this.elementRef.nativeElement;
|
|
@@ -413,6 +426,9 @@ class GridFormArray {
|
|
|
413
426
|
if (this.touchListener) {
|
|
414
427
|
grid.removeEventListener('click', this.touchListener);
|
|
415
428
|
}
|
|
429
|
+
if (this.valueChangesSubscription) {
|
|
430
|
+
this.valueChangesSubscription.unsubscribe();
|
|
431
|
+
}
|
|
416
432
|
this.#clearFormContext(grid);
|
|
417
433
|
}
|
|
418
434
|
/**
|
|
@@ -1268,14 +1284,39 @@ class AngularGridAdapter {
|
|
|
1268
1284
|
* @returns Processed GridConfig with actual renderer/editor functions
|
|
1269
1285
|
*/
|
|
1270
1286
|
processGridConfig(config) {
|
|
1271
|
-
|
|
1272
|
-
|
|
1287
|
+
const result = { ...config };
|
|
1288
|
+
// Process columns
|
|
1289
|
+
if (config.columns) {
|
|
1290
|
+
result.columns = config.columns.map((col) => this.processColumn(col));
|
|
1273
1291
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1292
|
+
// Process typeDefaults - convert Angular component classes to renderer/editor functions
|
|
1293
|
+
if (config.typeDefaults) {
|
|
1294
|
+
result.typeDefaults = this.processTypeDefaults(config.typeDefaults);
|
|
1295
|
+
}
|
|
1296
|
+
return result;
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Processes typeDefaults configuration, converting component class references
|
|
1300
|
+
* to actual renderer/editor functions.
|
|
1301
|
+
*
|
|
1302
|
+
* @param typeDefaults - Angular type defaults with possible component class references
|
|
1303
|
+
* @returns Processed TypeDefault record
|
|
1304
|
+
*/
|
|
1305
|
+
processTypeDefaults(typeDefaults) {
|
|
1306
|
+
const processed = {};
|
|
1307
|
+
for (const [type, config] of Object.entries(typeDefaults)) {
|
|
1308
|
+
const processedConfig = { ...config };
|
|
1309
|
+
// Convert renderer component class to function
|
|
1310
|
+
if (config.renderer && isComponentClass(config.renderer)) {
|
|
1311
|
+
processedConfig.renderer = this.createComponentRenderer(config.renderer);
|
|
1312
|
+
}
|
|
1313
|
+
// Convert editor component class to function
|
|
1314
|
+
if (config.editor && isComponentClass(config.editor)) {
|
|
1315
|
+
processedConfig.editor = this.createComponentEditor(config.editor);
|
|
1316
|
+
}
|
|
1317
|
+
processed[type] = processedConfig;
|
|
1318
|
+
}
|
|
1319
|
+
return processed;
|
|
1279
1320
|
}
|
|
1280
1321
|
/**
|
|
1281
1322
|
* Processes a single column configuration, converting component class references
|
|
@@ -1318,6 +1359,11 @@ class AngularGridAdapter {
|
|
|
1318
1359
|
return undefined;
|
|
1319
1360
|
}
|
|
1320
1361
|
return (ctx) => {
|
|
1362
|
+
// Skip rendering if the cell is in editing mode
|
|
1363
|
+
// This prevents the renderer from overwriting the editor when the grid re-renders
|
|
1364
|
+
if (ctx.cellEl?.classList.contains('editing')) {
|
|
1365
|
+
return null;
|
|
1366
|
+
}
|
|
1321
1367
|
// Create the context for the template
|
|
1322
1368
|
const context = {
|
|
1323
1369
|
$implicit: ctx.value,
|
|
@@ -1358,10 +1404,10 @@ class AngularGridAdapter {
|
|
|
1358
1404
|
// Find the parent grid element for FormArray context access
|
|
1359
1405
|
const gridElement = element.closest('tbw-grid');
|
|
1360
1406
|
if (!template) {
|
|
1361
|
-
// No
|
|
1362
|
-
//
|
|
1363
|
-
//
|
|
1364
|
-
return
|
|
1407
|
+
// No template registered - return undefined to let the grid use its default editor.
|
|
1408
|
+
// This allows columns with only *tbwRenderer (no *tbwEditor) to still be editable
|
|
1409
|
+
// using the built-in text/number/boolean editors.
|
|
1410
|
+
return undefined;
|
|
1365
1411
|
}
|
|
1366
1412
|
return (ctx) => {
|
|
1367
1413
|
// Create simple callback functions (preferred)
|
|
@@ -1591,31 +1637,58 @@ class AngularGridAdapter {
|
|
|
1591
1637
|
}
|
|
1592
1638
|
return typeDefault;
|
|
1593
1639
|
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Creates and mounts an Angular component dynamically.
|
|
1642
|
+
* Shared logic between renderer and editor component creation.
|
|
1643
|
+
* @internal
|
|
1644
|
+
*/
|
|
1645
|
+
mountComponent(componentClass, inputs) {
|
|
1646
|
+
// Create a host element for the component
|
|
1647
|
+
const hostElement = document.createElement('span');
|
|
1648
|
+
hostElement.style.display = 'contents';
|
|
1649
|
+
// Create the component dynamically
|
|
1650
|
+
const componentRef = createComponent(componentClass, {
|
|
1651
|
+
environmentInjector: this.injector,
|
|
1652
|
+
hostElement,
|
|
1653
|
+
});
|
|
1654
|
+
// Set inputs - components should have value, row, column inputs
|
|
1655
|
+
this.setComponentInputs(componentRef, inputs);
|
|
1656
|
+
// Attach to app for change detection
|
|
1657
|
+
this.appRef.attachView(componentRef.hostView);
|
|
1658
|
+
this.componentRefs.push(componentRef);
|
|
1659
|
+
// Trigger change detection
|
|
1660
|
+
componentRef.changeDetectorRef.detectChanges();
|
|
1661
|
+
return { hostElement, componentRef };
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Wires up commit/cancel handlers for an editor component.
|
|
1665
|
+
* Supports both Angular outputs and DOM CustomEvents.
|
|
1666
|
+
* @internal
|
|
1667
|
+
*/
|
|
1668
|
+
wireEditorCallbacks(hostElement, componentRef, commit, cancel) {
|
|
1669
|
+
// Subscribe to Angular outputs (commit/cancel) on the component instance.
|
|
1670
|
+
// This works with Angular's output() signal API.
|
|
1671
|
+
const instance = componentRef.instance;
|
|
1672
|
+
this.subscribeToOutput(instance, 'commit', commit);
|
|
1673
|
+
this.subscribeToOutput(instance, 'cancel', cancel);
|
|
1674
|
+
// Also listen for DOM events as fallback (for components that dispatch CustomEvents)
|
|
1675
|
+
hostElement.addEventListener('commit', (e) => {
|
|
1676
|
+
const customEvent = e;
|
|
1677
|
+
commit(customEvent.detail);
|
|
1678
|
+
});
|
|
1679
|
+
hostElement.addEventListener('cancel', () => cancel());
|
|
1680
|
+
}
|
|
1594
1681
|
/**
|
|
1595
1682
|
* Creates a renderer function from an Angular component class.
|
|
1596
1683
|
* @internal
|
|
1597
1684
|
*/
|
|
1598
1685
|
createComponentRenderer(componentClass) {
|
|
1599
1686
|
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, {
|
|
1687
|
+
const { hostElement } = this.mountComponent(componentClass, {
|
|
1610
1688
|
value: ctx.value,
|
|
1611
1689
|
row: ctx.row,
|
|
1612
1690
|
column: ctx.column,
|
|
1613
1691
|
});
|
|
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
1692
|
return hostElement;
|
|
1620
1693
|
};
|
|
1621
1694
|
}
|
|
@@ -1625,38 +1698,12 @@ class AngularGridAdapter {
|
|
|
1625
1698
|
*/
|
|
1626
1699
|
createComponentEditor(componentClass) {
|
|
1627
1700
|
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, {
|
|
1701
|
+
const { hostElement, componentRef } = this.mountComponent(componentClass, {
|
|
1638
1702
|
value: ctx.value,
|
|
1639
1703
|
row: ctx.row,
|
|
1640
1704
|
column: ctx.column,
|
|
1641
1705
|
});
|
|
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
|
-
});
|
|
1706
|
+
this.wireEditorCallbacks(hostElement, componentRef, (value) => ctx.commit(value), () => ctx.cancel());
|
|
1660
1707
|
return hostElement;
|
|
1661
1708
|
};
|
|
1662
1709
|
}
|
|
@@ -2697,6 +2744,9 @@ class Grid {
|
|
|
2697
2744
|
* <tbw-grid [editing]="'click'" />
|
|
2698
2745
|
* <tbw-grid [editing]="'dblclick'" />
|
|
2699
2746
|
* <tbw-grid [editing]="'manual'" />
|
|
2747
|
+
*
|
|
2748
|
+
* <!-- Full config with callbacks -->
|
|
2749
|
+
* <tbw-grid [editing]="{ editOn: 'dblclick', onBeforeEditClose: myCallback }" />
|
|
2700
2750
|
* ```
|
|
2701
2751
|
*/
|
|
2702
2752
|
editing = input(...(ngDevMode ? [undefined, { debugName: "editing" }] : []));
|