@toolbox-web/grid-angular 0.8.0 → 0.9.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.
|
@@ -341,11 +341,23 @@ function getFormArrayContext(gridElement) {
|
|
|
341
341
|
class GridFormArray {
|
|
342
342
|
elementRef = inject((ElementRef));
|
|
343
343
|
cellCommitListener = null;
|
|
344
|
+
rowCommitListener = null;
|
|
344
345
|
touchListener = null;
|
|
345
346
|
/**
|
|
346
347
|
* The FormArray to bind to the grid.
|
|
347
348
|
*/
|
|
348
349
|
formArray = input.required(...(ngDevMode ? [{ debugName: "formArray" }] : []));
|
|
350
|
+
/**
|
|
351
|
+
* Whether to automatically sync Angular validation state to grid's visual invalid styling.
|
|
352
|
+
*
|
|
353
|
+
* When enabled:
|
|
354
|
+
* - After a cell commit, if the FormControl is invalid, the cell is marked with `setInvalid()`
|
|
355
|
+
* - When a FormControl becomes valid, `clearInvalid()` is called
|
|
356
|
+
* - On `row-commit`, if the row's FormGroup has invalid controls, the commit is prevented
|
|
357
|
+
*
|
|
358
|
+
* @default true
|
|
359
|
+
*/
|
|
360
|
+
syncValidation = input(true, ...(ngDevMode ? [{ debugName: "syncValidation" }] : []));
|
|
349
361
|
/**
|
|
350
362
|
* Effect that syncs the FormArray value to the grid rows.
|
|
351
363
|
*/
|
|
@@ -369,6 +381,14 @@ class GridFormArray {
|
|
|
369
381
|
this.#handleCellCommit(detail);
|
|
370
382
|
};
|
|
371
383
|
grid.addEventListener('cell-commit', this.cellCommitListener);
|
|
384
|
+
// Intercept row-commit events to prevent if FormGroup is invalid
|
|
385
|
+
this.rowCommitListener = (e) => {
|
|
386
|
+
if (!this.syncValidation())
|
|
387
|
+
return;
|
|
388
|
+
const detail = e.detail;
|
|
389
|
+
this.#handleRowCommit(e, detail);
|
|
390
|
+
};
|
|
391
|
+
grid.addEventListener('row-commit', this.rowCommitListener);
|
|
372
392
|
// Mark FormArray as touched on first interaction
|
|
373
393
|
this.touchListener = () => {
|
|
374
394
|
this.formArray().markAsTouched();
|
|
@@ -387,6 +407,9 @@ class GridFormArray {
|
|
|
387
407
|
if (this.cellCommitListener) {
|
|
388
408
|
grid.removeEventListener('cell-commit', this.cellCommitListener);
|
|
389
409
|
}
|
|
410
|
+
if (this.rowCommitListener) {
|
|
411
|
+
grid.removeEventListener('row-commit', this.rowCommitListener);
|
|
412
|
+
}
|
|
390
413
|
if (this.touchListener) {
|
|
391
414
|
grid.removeEventListener('click', this.touchListener);
|
|
392
415
|
}
|
|
@@ -491,7 +514,7 @@ class GridFormArray {
|
|
|
491
514
|
* Handles cell-commit events by updating the FormControl in the FormGroup.
|
|
492
515
|
*/
|
|
493
516
|
#handleCellCommit(detail) {
|
|
494
|
-
const { rowIndex, field, value } = detail;
|
|
517
|
+
const { rowIndex, field, value, rowId } = detail;
|
|
495
518
|
const rowFormGroup = this.#getRowFormGroup(rowIndex);
|
|
496
519
|
if (rowFormGroup) {
|
|
497
520
|
const control = rowFormGroup.get(field);
|
|
@@ -499,18 +522,83 @@ class GridFormArray {
|
|
|
499
522
|
control.setValue(value);
|
|
500
523
|
control.markAsDirty();
|
|
501
524
|
control.markAsTouched();
|
|
525
|
+
// Sync Angular validation state to grid's visual invalid styling
|
|
526
|
+
if (this.syncValidation() && rowId) {
|
|
527
|
+
this.#syncControlValidationToGrid(rowId, field, control);
|
|
528
|
+
}
|
|
502
529
|
}
|
|
503
530
|
}
|
|
504
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* Handles row-commit events - prevents commit if FormGroup has invalid controls.
|
|
534
|
+
*/
|
|
535
|
+
#handleRowCommit(event, detail) {
|
|
536
|
+
const { rowIndex } = detail;
|
|
537
|
+
const rowFormGroup = this.#getRowFormGroup(rowIndex);
|
|
538
|
+
if (rowFormGroup && rowFormGroup.invalid) {
|
|
539
|
+
// Prevent row commit if the FormGroup is invalid
|
|
540
|
+
event.preventDefault();
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Syncs a FormControl's validation state to the grid's visual invalid styling.
|
|
545
|
+
*/
|
|
546
|
+
#syncControlValidationToGrid(rowId, field, control) {
|
|
547
|
+
const grid = this.elementRef.nativeElement;
|
|
548
|
+
if (!grid)
|
|
549
|
+
return;
|
|
550
|
+
// Get EditingPlugin via getPluginByName
|
|
551
|
+
const editingPlugin = grid.getPluginByName?.('editing');
|
|
552
|
+
if (!editingPlugin)
|
|
553
|
+
return;
|
|
554
|
+
if (control.invalid) {
|
|
555
|
+
// Get first error message to display
|
|
556
|
+
const errorMessage = this.#getFirstErrorMessage(control);
|
|
557
|
+
editingPlugin.setInvalid(rowId, field, errorMessage);
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
editingPlugin.clearInvalid(rowId, field);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Gets a human-readable error message from the first validation error.
|
|
565
|
+
*/
|
|
566
|
+
#getFirstErrorMessage(control) {
|
|
567
|
+
const errors = control.errors;
|
|
568
|
+
if (!errors)
|
|
569
|
+
return '';
|
|
570
|
+
const firstKey = Object.keys(errors)[0];
|
|
571
|
+
const error = errors[firstKey];
|
|
572
|
+
// Common Angular validators
|
|
573
|
+
switch (firstKey) {
|
|
574
|
+
case 'required':
|
|
575
|
+
return 'This field is required';
|
|
576
|
+
case 'minlength':
|
|
577
|
+
return `Minimum length is ${error.requiredLength}`;
|
|
578
|
+
case 'maxlength':
|
|
579
|
+
return `Maximum length is ${error.requiredLength}`;
|
|
580
|
+
case 'min':
|
|
581
|
+
return `Minimum value is ${error.min}`;
|
|
582
|
+
case 'max':
|
|
583
|
+
return `Maximum value is ${error.max}`;
|
|
584
|
+
case 'email':
|
|
585
|
+
return 'Invalid email address';
|
|
586
|
+
case 'pattern':
|
|
587
|
+
return 'Invalid format';
|
|
588
|
+
default:
|
|
589
|
+
// Custom validators may provide a message property
|
|
590
|
+
return typeof error === 'string' ? error : (error?.message ?? `Validation error: ${firstKey}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
505
593
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridFormArray, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
506
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.1", type: GridFormArray, isStandalone: true, selector: "tbw-grid[formArray]", inputs: { formArray: { classPropertyName: "formArray", publicName: "formArray", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
|
|
594
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.1", type: GridFormArray, isStandalone: true, selector: "tbw-grid[formArray]", inputs: { formArray: { classPropertyName: "formArray", publicName: "formArray", isSignal: true, isRequired: true, transformFunction: null }, syncValidation: { classPropertyName: "syncValidation", publicName: "syncValidation", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
507
595
|
}
|
|
508
596
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: GridFormArray, decorators: [{
|
|
509
597
|
type: Directive,
|
|
510
598
|
args: [{
|
|
511
599
|
selector: 'tbw-grid[formArray]',
|
|
512
600
|
}]
|
|
513
|
-
}], propDecorators: { formArray: [{ type: i0.Input, args: [{ isSignal: true, alias: "formArray", required: true }] }] } });
|
|
601
|
+
}], propDecorators: { formArray: [{ type: i0.Input, args: [{ isSignal: true, alias: "formArray", required: true }] }], syncValidation: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncValidation", required: false }] }] } });
|
|
514
602
|
|
|
515
603
|
/**
|
|
516
604
|
* Registry to store responsive card templates by grid element.
|