@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.