@ng-modular-forms/core 0.8.1 → 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.
@@ -1,7 +1,7 @@
1
1
  import * as i2 from '@angular/forms';
2
- import { NgControl, Validators, TouchedChangeEvent, ReactiveFormsModule, FormGroup } from '@angular/forms';
2
+ import { NgControl, Validators, TouchedChangeEvent, ReactiveFormsModule, FormArray, FormGroup, FormControl } from '@angular/forms';
3
3
  import * as i0 from '@angular/core';
4
- import { viewChild, input, computed, booleanAttribute, signal, inject, ChangeDetectorRef, DestroyRef, ElementRef, Directive, ChangeDetectionStrategy, Component, effect, Injectable } from '@angular/core';
4
+ import { viewChild, input, computed, booleanAttribute, signal, inject, ChangeDetectorRef, DestroyRef, ElementRef, Directive, ChangeDetectionStrategy, Component, effect, contentChild, Injectable } from '@angular/core';
5
5
  import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
6
6
  import { merge, startWith, Subject, take, catchError, EMPTY, map, debounceTime, distinctUntilChanged, filter, tap, switchMap, of, Subscription } from 'rxjs';
7
7
  import * as i1 from '@angular/common';
@@ -253,8 +253,28 @@ class FormMapperBase {
253
253
  }
254
254
  }
255
255
 
256
- class CurrencyBehavior {
257
- blockNonDigitKey(event) {
256
+ class NumberBehavior {
257
+ sanitize(value, allowNegative) {
258
+ if (!value)
259
+ return '';
260
+ let cleaned = value.replace(/[^0-9.-]/g, '');
261
+ const isJustMinus = cleaned === '-';
262
+ if (isJustMinus && allowNegative)
263
+ return '-';
264
+ const parts = cleaned.split('.');
265
+ if (parts.length > 2) {
266
+ cleaned = parts[0] + '.' + parts.slice(1).join('');
267
+ }
268
+ const hasMinus = cleaned.includes('-');
269
+ if (hasMinus) {
270
+ cleaned = cleaned.replace(/-/g, '');
271
+ if (allowNegative) {
272
+ cleaned = '-' + cleaned;
273
+ }
274
+ }
275
+ return cleaned;
276
+ }
277
+ blockNonDigitKey(event, allowNegative = true) {
258
278
  const input = event.target;
259
279
  const value = input.value;
260
280
  if (event.ctrlKey || event.metaKey) {
@@ -289,8 +309,15 @@ class CurrencyBehavior {
289
309
  return;
290
310
  }
291
311
  if (event.key === '-') {
312
+ if (!allowNegative) {
313
+ event.preventDefault();
314
+ return;
315
+ }
292
316
  const hasMinus = value.includes('-');
293
- const isAtStart = input.selectionStart === 0;
317
+ const el = event.target ??
318
+ event.currentTarget;
319
+ const pos = el?.selectionStart != null ? el.selectionStart : value.length;
320
+ const isAtStart = pos === 0;
294
321
  if (hasMinus || !isAtStart) {
295
322
  event.preventDefault();
296
323
  }
@@ -419,7 +446,7 @@ class LookupBehavior {
419
446
  }
420
447
  }
421
448
 
422
- class TextBehavior {
449
+ class PasswordBehavior {
423
450
  _showPassword = signal(false);
424
451
  showPassword = this._showPassword.asReadonly();
425
452
  toggleShowPassword(event) {
@@ -430,14 +457,14 @@ class TextBehavior {
430
457
 
431
458
  class FormFieldComponent {
432
459
  label = input();
433
- isRequired = input();
434
460
  loading = input();
435
461
  hint = input();
462
+ isRequired = input();
436
463
  errorMessage = input();
437
- hasErrors = computed(() => this.errorMessage() != null);
464
+ hasErrors = computed(() => !!this.errorMessage());
438
465
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: FormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
439
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: FormFieldComponent, isStandalone: true, selector: "nmf-form-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, isRequired: { classPropertyName: "isRequired", publicName: "isRequired", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
440
- <div class="nmf-field" [class.loading]="loading()">
466
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: FormFieldComponent, isStandalone: true, selector: "nmf-form-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, isRequired: { classPropertyName: "isRequired", publicName: "isRequired", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
467
+ <div class="nmf-field-wrapper" [class.loading]="loading()">
441
468
  @if (label()) {
442
469
  <label class="nmf-label">
443
470
  {{ label() }}
@@ -475,7 +502,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
475
502
  imports: [CommonModule, ReactiveFormsModule],
476
503
  changeDetection: ChangeDetectionStrategy.OnPush,
477
504
  template: `
478
- <div class="nmf-field" [class.loading]="loading()">
505
+ <div class="nmf-field-wrapper" [class.loading]="loading()">
479
506
  @if (label()) {
480
507
  <label class="nmf-label">
481
508
  {{ label() }}
@@ -507,129 +534,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
507
534
  }]
508
535
  }] });
509
536
 
510
- class InputCurrencyComponent extends FormControlBase {
511
- displayValue = signal(null);
512
- behavior = new CurrencyBehavior();
513
- writeValue(value) {
514
- super.writeValue(value);
515
- this.displayValue.set(value != null ? formatNumber(value) : null);
516
- }
517
- handleKeyDown(event) {
518
- this.behavior.blockNonDigitKey(event);
519
- }
520
- onInput(event) {
521
- if (this._disabledByInput())
522
- return;
523
- const rawValue = event.target.value ?? null;
524
- const value = parseNumber(rawValue);
525
- this.displayValue.set(value != null ? formatNumber(value) : null);
526
- this.onChange(value);
527
- }
528
- textColor = computed(() => {
529
- const value = this.displayValue();
530
- if (this._disabledByInput() || !value) {
531
- return 'inherit';
532
- }
533
- const parsedValue = parseNumber(value);
534
- const valid = parsedValue != null && parsedValue >= 0;
535
- return valid ? 'inherit' : '#dc6262';
536
- });
537
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InputCurrencyComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
538
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: InputCurrencyComponent, isStandalone: true, selector: "nmf-currency", usesInheritance: true, ngImport: i0, template: `
539
- <nmf-form-field
540
- [label]="label()"
541
- [isRequired]="isRequired()"
542
- [loading]="loading()"
543
- [errorMessage]="errorMessage()"
544
- >
545
- <div class="nmf-input-wrapper nmf-input-prefix">
546
- @if (displayValue() != null) {
547
- <span
548
- class="nmf-prefix"
549
- [class.error]="hasErrors()"
550
- [class.nmf-prefix-disabled]="disabled()"
551
- [style.color]="textColor()"
552
- >
553
- $
554
- </span>
555
- }
556
-
557
- <input
558
- #focusable
559
- type="text"
560
- class="nmf-input"
561
- [ngClass]="classList()"
562
- [class.error]="hasErrors()"
563
- [class.disabled]="disabled()"
564
- [class.nmf-input-with-prefix]="displayValue() != null"
565
- [style.color]="textColor()"
566
- [id]="id()"
567
- [name]="name()"
568
- [value]="displayValue()"
569
- [disabled]="disabled()"
570
- [required]="isRequired()"
571
- [placeholder]="placeholder()"
572
- [autocomplete]="autocompleteAttr()"
573
- (blur)="onTouched()"
574
- (input)="onInput($event)"
575
- (keydown)="handleKeyDown($event)"
576
- />
577
- </div>
578
- </nmf-form-field>
579
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "isRequired", "loading", "hint", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
580
- }
581
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InputCurrencyComponent, decorators: [{
582
- type: Component,
583
- args: [{
584
- selector: 'nmf-currency',
585
- standalone: true,
586
- imports: [CommonModule, ReactiveFormsModule, FormFieldComponent],
587
- changeDetection: ChangeDetectionStrategy.OnPush,
588
- template: `
589
- <nmf-form-field
590
- [label]="label()"
591
- [isRequired]="isRequired()"
592
- [loading]="loading()"
593
- [errorMessage]="errorMessage()"
594
- >
595
- <div class="nmf-input-wrapper nmf-input-prefix">
596
- @if (displayValue() != null) {
597
- <span
598
- class="nmf-prefix"
599
- [class.error]="hasErrors()"
600
- [class.nmf-prefix-disabled]="disabled()"
601
- [style.color]="textColor()"
602
- >
603
- $
604
- </span>
605
- }
606
-
607
- <input
608
- #focusable
609
- type="text"
610
- class="nmf-input"
611
- [ngClass]="classList()"
612
- [class.error]="hasErrors()"
613
- [class.disabled]="disabled()"
614
- [class.nmf-input-with-prefix]="displayValue() != null"
615
- [style.color]="textColor()"
616
- [id]="id()"
617
- [name]="name()"
618
- [value]="displayValue()"
619
- [disabled]="disabled()"
620
- [required]="isRequired()"
621
- [placeholder]="placeholder()"
622
- [autocomplete]="autocompleteAttr()"
623
- (blur)="onTouched()"
624
- (input)="onInput($event)"
625
- (keydown)="handleKeyDown($event)"
626
- />
627
- </div>
628
- </nmf-form-field>
629
- `,
630
- }]
631
- }] });
632
-
633
537
  class InputDatepickerComponent extends FormControlBase {
634
538
  minDate = input(null);
635
539
  maxDate = input(null);
@@ -669,27 +573,31 @@ class InputDatepickerComponent extends FormControlBase {
669
573
  [loading]="loading()"
670
574
  [errorMessage]="errorMessage()"
671
575
  >
672
- <input
673
- #focusable
674
- type="date"
675
- class="nmf-input"
676
- [ngClass]="classList()"
576
+ <div
577
+ class="nmf-control-wrapper"
677
578
  [class.error]="hasErrors()"
678
579
  [class.disabled]="disabled()"
679
- [id]="id()"
680
- [min]="formatDate(minDate())"
681
- [max]="formatDate(maxDate())"
682
- [name]="name()"
683
- [value]="displayValue()"
684
- [disabled]="disabled()"
685
- [required]="isRequired()"
686
- [placeholder]="placeholder()"
687
- [autocomplete]="autocompleteAttr()"
688
- (blur)="onTouched()"
689
- (input)="onInput($event)"
690
- />
580
+ >
581
+ <input
582
+ #focusable
583
+ type="date"
584
+ class="nmf-control"
585
+ [ngClass]="classList()"
586
+ [id]="id()"
587
+ [min]="formatDate(minDate())"
588
+ [max]="formatDate(maxDate())"
589
+ [name]="name()"
590
+ [value]="displayValue()"
591
+ [disabled]="disabled()"
592
+ [required]="isRequired()"
593
+ [placeholder]="placeholder()"
594
+ [autocomplete]="autocompleteAttr()"
595
+ (blur)="onTouched()"
596
+ (input)="onInput($event)"
597
+ />
598
+ </div>
691
599
  </nmf-form-field>
692
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "isRequired", "loading", "hint", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
600
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "loading", "hint", "isRequired", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
693
601
  }
694
602
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InputDatepickerComponent, decorators: [{
695
603
  type: Component,
@@ -705,25 +613,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
705
613
  [loading]="loading()"
706
614
  [errorMessage]="errorMessage()"
707
615
  >
708
- <input
709
- #focusable
710
- type="date"
711
- class="nmf-input"
712
- [ngClass]="classList()"
616
+ <div
617
+ class="nmf-control-wrapper"
713
618
  [class.error]="hasErrors()"
714
619
  [class.disabled]="disabled()"
715
- [id]="id()"
716
- [min]="formatDate(minDate())"
717
- [max]="formatDate(maxDate())"
718
- [name]="name()"
719
- [value]="displayValue()"
720
- [disabled]="disabled()"
721
- [required]="isRequired()"
722
- [placeholder]="placeholder()"
723
- [autocomplete]="autocompleteAttr()"
724
- (blur)="onTouched()"
725
- (input)="onInput($event)"
726
- />
620
+ >
621
+ <input
622
+ #focusable
623
+ type="date"
624
+ class="nmf-control"
625
+ [ngClass]="classList()"
626
+ [id]="id()"
627
+ [min]="formatDate(minDate())"
628
+ [max]="formatDate(maxDate())"
629
+ [name]="name()"
630
+ [value]="displayValue()"
631
+ [disabled]="disabled()"
632
+ [required]="isRequired()"
633
+ [placeholder]="placeholder()"
634
+ [autocomplete]="autocompleteAttr()"
635
+ (blur)="onTouched()"
636
+ (input)="onInput($event)"
637
+ />
638
+ </div>
727
639
  </nmf-form-field>
728
640
  `,
729
641
  }]
@@ -906,16 +818,18 @@ class InputLookupComponent extends FormControlBase {
906
818
  [loading]="behavior.status() === 'loading' || loading()"
907
819
  [errorMessage]="errorMessage()"
908
820
  >
909
- <div class="nmf-input-wrapper" [style.position]="'relative'">
910
- @if ({ options: behavior.filteredOptions | async }; as stream) {
821
+ <div
822
+ class="nmf-control-wrapper"
823
+ [class.error]="hasErrors()"
824
+ [class.disabled]="disabled()"
825
+ >
826
+ @if (behavior.filteredOptions | async; as options) {
911
827
  <input
912
828
  #focusable
913
829
  type="text"
914
- class="nmf-input"
830
+ class="nmf-control"
915
831
  [attr.list]="id + '-options'"
916
832
  [ngClass]="classList()"
917
- [class.error]="hasErrors()"
918
- [class.disabled]="disabled()"
919
833
  [style.cursor]="
920
834
  behavior.selectedOption() != null ? 'not-allowed' : 'text'
921
835
  "
@@ -930,17 +844,17 @@ class InputLookupComponent extends FormControlBase {
930
844
  (blur)="onFocusOut()"
931
845
  (focus)="onFocusIn()"
932
846
  (input)="onInput($event)"
933
- (keydown)="onKeyDown($event, stream.options)"
847
+ (keydown)="onKeyDown($event, options)"
934
848
  />
935
849
 
936
- @if (isOpen() && stream.options && stream.options.length > 0) {
850
+ @if (isOpen() && options && options.length > 0) {
937
851
  <ul
938
852
  class="nmf-options-list"
939
853
  [class.upward]="openUpwards()"
940
854
  (pointerdown)="setOptionsInteraction(true)"
941
855
  (pointerup)="setOptionsInteraction(false)"
942
856
  >
943
- @for (option of stream.options; track option; let i = $index) {
857
+ @for (option of options; track option; let i = $index) {
944
858
  <li
945
859
  [class.selected]="behavior.selectedOption() == option"
946
860
  [class.active]="activeIndex() === i"
@@ -967,7 +881,7 @@ class InputLookupComponent extends FormControlBase {
967
881
  }
968
882
  </div>
969
883
  </nmf-form-field>
970
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "isRequired", "loading", "hint", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
884
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "loading", "hint", "isRequired", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
971
885
  }
972
886
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InputLookupComponent, decorators: [{
973
887
  type: Component,
@@ -984,16 +898,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
984
898
  [loading]="behavior.status() === 'loading' || loading()"
985
899
  [errorMessage]="errorMessage()"
986
900
  >
987
- <div class="nmf-input-wrapper" [style.position]="'relative'">
988
- @if ({ options: behavior.filteredOptions | async }; as stream) {
901
+ <div
902
+ class="nmf-control-wrapper"
903
+ [class.error]="hasErrors()"
904
+ [class.disabled]="disabled()"
905
+ >
906
+ @if (behavior.filteredOptions | async; as options) {
989
907
  <input
990
908
  #focusable
991
909
  type="text"
992
- class="nmf-input"
910
+ class="nmf-control"
993
911
  [attr.list]="id + '-options'"
994
912
  [ngClass]="classList()"
995
- [class.error]="hasErrors()"
996
- [class.disabled]="disabled()"
997
913
  [style.cursor]="
998
914
  behavior.selectedOption() != null ? 'not-allowed' : 'text'
999
915
  "
@@ -1008,17 +924,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1008
924
  (blur)="onFocusOut()"
1009
925
  (focus)="onFocusIn()"
1010
926
  (input)="onInput($event)"
1011
- (keydown)="onKeyDown($event, stream.options)"
927
+ (keydown)="onKeyDown($event, options)"
1012
928
  />
1013
929
 
1014
- @if (isOpen() && stream.options && stream.options.length > 0) {
930
+ @if (isOpen() && options && options.length > 0) {
1015
931
  <ul
1016
932
  class="nmf-options-list"
1017
933
  [class.upward]="openUpwards()"
1018
934
  (pointerdown)="setOptionsInteraction(true)"
1019
935
  (pointerup)="setOptionsInteraction(false)"
1020
936
  >
1021
- @for (option of stream.options; track option; let i = $index) {
937
+ @for (option of options; track option; let i = $index) {
1022
938
  <li
1023
939
  [class.selected]="behavior.selectedOption() == option"
1024
940
  [class.active]="activeIndex() === i"
@@ -1049,62 +965,127 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1049
965
  }]
1050
966
  }], ctorParameters: () => [] });
1051
967
 
968
+ class NmfPrefixDirective {
969
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: NmfPrefixDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
970
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", type: NmfPrefixDirective, isStandalone: true, selector: "[nmfPrefix]", ngImport: i0 });
971
+ }
972
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: NmfPrefixDirective, decorators: [{
973
+ type: Directive,
974
+ args: [{
975
+ selector: '[nmfPrefix]',
976
+ standalone: true,
977
+ }]
978
+ }] });
979
+
980
+ class NmfSuffixDirective {
981
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: NmfSuffixDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
982
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.25", type: NmfSuffixDirective, isStandalone: true, selector: "[nmfSuffix]", ngImport: i0 });
983
+ }
984
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: NmfSuffixDirective, decorators: [{
985
+ type: Directive,
986
+ args: [{
987
+ selector: '[nmfSuffix]',
988
+ standalone: true,
989
+ }]
990
+ }] });
991
+
1052
992
  class InputNumberComponent extends FormControlBase {
1053
993
  formatValue = input(false);
994
+ prefix = input(null);
995
+ suffix = input(null);
996
+ allowNegative = input(true);
997
+ negativeColor = input('var(--mat-sys-error)');
1054
998
  displayValue = signal('');
1055
- behavior = new TextBehavior();
1056
- currencyBehavior = new CurrencyBehavior();
999
+ numberBehavior = new NumberBehavior();
1000
+ prefixContent = contentChild(NmfPrefixDirective);
1001
+ suffixContent = contentChild(NmfSuffixDirective);
1002
+ hasPrefix = computed(() => !!this.prefix() || !!this.prefixContent());
1003
+ hasSuffix = computed(() => !!this.suffix() || !!this.suffixContent());
1004
+ textColor = computed(() => {
1005
+ const value = this.displayValue();
1006
+ if (value == null ||
1007
+ value === '' ||
1008
+ this.negativeColor() == null ||
1009
+ this._disabledByInput()) {
1010
+ return 'inherit';
1011
+ }
1012
+ const parsed = parseNumber(value ?? 0);
1013
+ const valid = parsed != null && parsed >= 0;
1014
+ return valid ? 'inherit' : this.negativeColor();
1015
+ });
1057
1016
  writeValue(value) {
1058
1017
  super.writeValue(value);
1059
1018
  this.updateDisplayValue(value);
1060
1019
  }
1061
1020
  handleKeyDown(event) {
1062
- this.currencyBehavior.blockNonDigitKey(event);
1021
+ this.numberBehavior.blockNonDigitKey(event, this.allowNegative());
1063
1022
  }
1064
1023
  onInput(event) {
1065
1024
  if (this._disabledByInput())
1066
1025
  return;
1067
- const raw = event.target.value;
1068
- const parsed = parseNumber(raw);
1026
+ const raw = event.target.value ?? '';
1027
+ const cleaned = this.numberBehavior.sanitize(raw, this.allowNegative());
1028
+ const parsed = parseNumber(cleaned);
1069
1029
  this.updateDisplayValue(parsed);
1070
1030
  this.onChange(parsed);
1071
1031
  }
1072
1032
  updateDisplayValue(value) {
1073
1033
  if (this.formatValue() && value != null) {
1074
1034
  this.displayValue.set(formatNumber(value) ?? '');
1035
+ return;
1075
1036
  }
1076
- else {
1077
- this.displayValue.set(value != null ? String(value) : '');
1078
- }
1037
+ this.displayValue.set(value != null ? String(value) : '');
1079
1038
  }
1080
1039
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InputNumberComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1081
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.25", type: InputNumberComponent, isStandalone: true, selector: "nmf-number", inputs: { formatValue: { classPropertyName: "formatValue", publicName: "formatValue", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: `
1040
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: InputNumberComponent, isStandalone: true, selector: "nmf-number", inputs: { formatValue: { classPropertyName: "formatValue", publicName: "formatValue", isSignal: true, isRequired: false, transformFunction: null }, prefix: { classPropertyName: "prefix", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, allowNegative: { classPropertyName: "allowNegative", publicName: "allowNegative", isSignal: true, isRequired: false, transformFunction: null }, negativeColor: { classPropertyName: "negativeColor", publicName: "negativeColor", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "prefixContent", first: true, predicate: NmfPrefixDirective, descendants: true, isSignal: true }, { propertyName: "suffixContent", first: true, predicate: NmfSuffixDirective, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
1082
1041
  <nmf-form-field
1083
1042
  [label]="label()"
1084
1043
  [isRequired]="isRequired()"
1085
1044
  [loading]="loading()"
1086
1045
  [errorMessage]="errorMessage()"
1087
1046
  >
1088
- <input
1089
- #focusable
1090
- class="nmf-input"
1091
- [ngClass]="classList()"
1047
+ <div
1048
+ class="nmf-control-wrapper"
1092
1049
  [class.error]="hasErrors()"
1093
1050
  [class.disabled]="disabled()"
1094
- [id]="id()"
1095
- [name]="name()"
1096
- [type]="formatValue() ? 'text' : 'number'"
1097
- [value]="displayValue()"
1098
- [disabled]="disabled()"
1099
- [required]="isRequired()"
1100
- [placeholder]="placeholder()"
1101
- [autocomplete]="autocompleteAttr()"
1102
- (blur)="onTouched()"
1103
- (input)="onInput($event)"
1104
- (keydown)="handleKeyDown($event)"
1105
- />
1051
+ [class.has-prefix]="hasPrefix()"
1052
+ [class.has-suffix]="hasSuffix()"
1053
+ [style.color]="textColor()"
1054
+ >
1055
+ @if (prefix() != null) {
1056
+ <span class="nmf-prefix">
1057
+ {{ prefix() }}
1058
+ </span>
1059
+ }
1060
+ <ng-content select="[nmfPrefix]" />
1061
+
1062
+ <input
1063
+ #focusable
1064
+ class="nmf-control"
1065
+ [style.color]="textColor()"
1066
+ [ngClass]="classList()"
1067
+ [id]="id()"
1068
+ [name]="name()"
1069
+ [type]="formatValue() ? 'text' : 'number'"
1070
+ [value]="displayValue()"
1071
+ [disabled]="disabled()"
1072
+ [required]="isRequired()"
1073
+ [placeholder]="placeholder()"
1074
+ [autocomplete]="autocompleteAttr()"
1075
+ (blur)="onTouched()"
1076
+ (input)="onInput($event)"
1077
+ (keydown)="handleKeyDown($event)"
1078
+ />
1079
+
1080
+ @if (suffix() != null) {
1081
+ <span class="nmf-suffix">
1082
+ {{ suffix() }}
1083
+ </span>
1084
+ }
1085
+ <ng-content select="[nmfSuffix]" />
1086
+ </div>
1106
1087
  </nmf-form-field>
1107
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "isRequired", "loading", "hint", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1088
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "loading", "hint", "isRequired", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1108
1089
  }
1109
1090
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InputNumberComponent, decorators: [{
1110
1091
  type: Component,
@@ -1120,24 +1101,46 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1120
1101
  [loading]="loading()"
1121
1102
  [errorMessage]="errorMessage()"
1122
1103
  >
1123
- <input
1124
- #focusable
1125
- class="nmf-input"
1126
- [ngClass]="classList()"
1104
+ <div
1105
+ class="nmf-control-wrapper"
1127
1106
  [class.error]="hasErrors()"
1128
1107
  [class.disabled]="disabled()"
1129
- [id]="id()"
1130
- [name]="name()"
1131
- [type]="formatValue() ? 'text' : 'number'"
1132
- [value]="displayValue()"
1133
- [disabled]="disabled()"
1134
- [required]="isRequired()"
1135
- [placeholder]="placeholder()"
1136
- [autocomplete]="autocompleteAttr()"
1137
- (blur)="onTouched()"
1138
- (input)="onInput($event)"
1139
- (keydown)="handleKeyDown($event)"
1140
- />
1108
+ [class.has-prefix]="hasPrefix()"
1109
+ [class.has-suffix]="hasSuffix()"
1110
+ [style.color]="textColor()"
1111
+ >
1112
+ @if (prefix() != null) {
1113
+ <span class="nmf-prefix">
1114
+ {{ prefix() }}
1115
+ </span>
1116
+ }
1117
+ <ng-content select="[nmfPrefix]" />
1118
+
1119
+ <input
1120
+ #focusable
1121
+ class="nmf-control"
1122
+ [style.color]="textColor()"
1123
+ [ngClass]="classList()"
1124
+ [id]="id()"
1125
+ [name]="name()"
1126
+ [type]="formatValue() ? 'text' : 'number'"
1127
+ [value]="displayValue()"
1128
+ [disabled]="disabled()"
1129
+ [required]="isRequired()"
1130
+ [placeholder]="placeholder()"
1131
+ [autocomplete]="autocompleteAttr()"
1132
+ (blur)="onTouched()"
1133
+ (input)="onInput($event)"
1134
+ (keydown)="handleKeyDown($event)"
1135
+ />
1136
+
1137
+ @if (suffix() != null) {
1138
+ <span class="nmf-suffix">
1139
+ {{ suffix() }}
1140
+ </span>
1141
+ }
1142
+ <ng-content select="[nmfSuffix]" />
1143
+ </div>
1141
1144
  </nmf-form-field>
1142
1145
  `,
1143
1146
  }]
@@ -1161,13 +1164,15 @@ class InputSelectComponent extends FormControlBase {
1161
1164
  [loading]="loading()"
1162
1165
  [errorMessage]="errorMessage()"
1163
1166
  >
1164
- <div class="select-wrapper" [class.disabled]="disabled()">
1167
+ <div
1168
+ class="nmf-control-wrapper nmf-select"
1169
+ [class.disabled]="disabled()"
1170
+ [class.error]="hasErrors()"
1171
+ >
1165
1172
  <select
1166
1173
  #focusable
1167
- class="nmf-input"
1174
+ class="nmf-control"
1168
1175
  [ngClass]="classList()"
1169
- [class.error]="hasErrors()"
1170
- [class.disabled]="disabled()"
1171
1176
  [id]="id()"
1172
1177
  [value]="value ?? ''"
1173
1178
  [disabled]="disabled()"
@@ -1190,7 +1195,7 @@ class InputSelectComponent extends FormControlBase {
1190
1195
  </select>
1191
1196
  </div>
1192
1197
  </nmf-form-field>
1193
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "isRequired", "loading", "hint", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1198
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "loading", "hint", "isRequired", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1194
1199
  }
1195
1200
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InputSelectComponent, decorators: [{
1196
1201
  type: Component,
@@ -1206,13 +1211,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1206
1211
  [loading]="loading()"
1207
1212
  [errorMessage]="errorMessage()"
1208
1213
  >
1209
- <div class="select-wrapper" [class.disabled]="disabled()">
1214
+ <div
1215
+ class="nmf-control-wrapper nmf-select"
1216
+ [class.disabled]="disabled()"
1217
+ [class.error]="hasErrors()"
1218
+ >
1210
1219
  <select
1211
1220
  #focusable
1212
- class="nmf-input"
1221
+ class="nmf-control"
1213
1222
  [ngClass]="classList()"
1214
- [class.error]="hasErrors()"
1215
- [class.disabled]="disabled()"
1216
1223
  [id]="id()"
1217
1224
  [value]="value ?? ''"
1218
1225
  [disabled]="disabled()"
@@ -1241,31 +1248,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1241
1248
 
1242
1249
  class InputTextComponent extends FormControlBase {
1243
1250
  type = input('text');
1244
- behavior = new TextBehavior();
1245
- computedType = computed(() => this.behavior.showPassword() && this.type() === 'password'
1251
+ prefix = input(null);
1252
+ suffix = input(null);
1253
+ displayValue = signal(null);
1254
+ passwordBehavior = new PasswordBehavior();
1255
+ prefixContent = contentChild(NmfPrefixDirective);
1256
+ suffixContent = contentChild(NmfSuffixDirective);
1257
+ hasPrefix = computed(() => !!this.prefix() || !!this.prefixContent());
1258
+ hasSuffix = computed(() => !!this.suffix() || !!this.suffixContent());
1259
+ computedType = computed(() => this.passwordBehavior.showPassword() && this.type() === 'password'
1246
1260
  ? 'text'
1247
1261
  : this.type());
1248
1262
  onInput(event) {
1249
1263
  if (this._disabledByInput())
1250
1264
  return;
1251
- const input = event.target;
1252
- this.onChange(input.value);
1265
+ const value = event.target.value ?? null;
1266
+ this.onChange(value);
1253
1267
  }
1254
1268
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InputTextComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1255
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: InputTextComponent, isStandalone: true, selector: "nmf-text", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: `
1269
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: InputTextComponent, isStandalone: true, selector: "nmf-text", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, prefix: { classPropertyName: "prefix", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "prefixContent", first: true, predicate: NmfPrefixDirective, descendants: true, isSignal: true }, { propertyName: "suffixContent", first: true, predicate: NmfSuffixDirective, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
1256
1270
  <nmf-form-field
1257
1271
  [label]="label()"
1258
1272
  [isRequired]="isRequired()"
1259
1273
  [loading]="loading()"
1260
1274
  [errorMessage]="errorMessage()"
1261
1275
  >
1262
- <div class="nmf-input-wrapper">
1276
+ <div
1277
+ class="nmf-control-wrapper"
1278
+ [class.error]="hasErrors()"
1279
+ [class.disabled]="disabled()"
1280
+ [class.has-prefix]="hasPrefix()"
1281
+ [class.has-suffix]="hasSuffix()"
1282
+ >
1283
+ @if (prefix() != null) {
1284
+ <span class="nmf-prefix">
1285
+ {{ prefix() }}
1286
+ </span>
1287
+ }
1288
+ <ng-content select="[nmfPrefix]" />
1289
+
1263
1290
  <input
1264
1291
  #focusable
1265
- class="nmf-input"
1292
+ class="nmf-control"
1266
1293
  [ngClass]="classList()"
1267
- [class.error]="hasErrors()"
1268
- [class.disabled]="disabled()"
1269
1294
  [id]="id()"
1270
1295
  [name]="name()"
1271
1296
  [type]="computedType()"
@@ -1283,14 +1308,21 @@ class InputTextComponent extends FormControlBase {
1283
1308
  type="button"
1284
1309
  class="nmf-password-toggle"
1285
1310
  aria-label="Toggle password visibility"
1286
- (click)="behavior.toggleShowPassword($event)"
1311
+ (click)="passwordBehavior.toggleShowPassword($event)"
1287
1312
  >
1288
- @if (behavior.showPassword()) {
1313
+ @if (passwordBehavior.showPassword()) {
1289
1314
  <ng-container [ngTemplateOutlet]="eyeOffIcon"></ng-container>
1290
1315
  } @else {
1291
1316
  <ng-container [ngTemplateOutlet]="eyeIcon"></ng-container>
1292
1317
  }
1293
1318
  </button>
1319
+ } @else if (type() !== 'password') {
1320
+ @if (suffix() != null) {
1321
+ <span class="nmf-suffix">
1322
+ {{ suffix() }}
1323
+ </span>
1324
+ }
1325
+ <ng-content select="[nmfSuffix]" />
1294
1326
  }
1295
1327
  </div>
1296
1328
  </nmf-form-field>
@@ -1329,7 +1361,7 @@ class InputTextComponent extends FormControlBase {
1329
1361
  />
1330
1362
  </svg>
1331
1363
  </ng-template>
1332
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "isRequired", "loading", "hint", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1364
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "loading", "hint", "isRequired", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1333
1365
  }
1334
1366
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InputTextComponent, decorators: [{
1335
1367
  type: Component,
@@ -1345,13 +1377,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1345
1377
  [loading]="loading()"
1346
1378
  [errorMessage]="errorMessage()"
1347
1379
  >
1348
- <div class="nmf-input-wrapper">
1380
+ <div
1381
+ class="nmf-control-wrapper"
1382
+ [class.error]="hasErrors()"
1383
+ [class.disabled]="disabled()"
1384
+ [class.has-prefix]="hasPrefix()"
1385
+ [class.has-suffix]="hasSuffix()"
1386
+ >
1387
+ @if (prefix() != null) {
1388
+ <span class="nmf-prefix">
1389
+ {{ prefix() }}
1390
+ </span>
1391
+ }
1392
+ <ng-content select="[nmfPrefix]" />
1393
+
1349
1394
  <input
1350
1395
  #focusable
1351
- class="nmf-input"
1396
+ class="nmf-control"
1352
1397
  [ngClass]="classList()"
1353
- [class.error]="hasErrors()"
1354
- [class.disabled]="disabled()"
1355
1398
  [id]="id()"
1356
1399
  [name]="name()"
1357
1400
  [type]="computedType()"
@@ -1369,14 +1412,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1369
1412
  type="button"
1370
1413
  class="nmf-password-toggle"
1371
1414
  aria-label="Toggle password visibility"
1372
- (click)="behavior.toggleShowPassword($event)"
1415
+ (click)="passwordBehavior.toggleShowPassword($event)"
1373
1416
  >
1374
- @if (behavior.showPassword()) {
1417
+ @if (passwordBehavior.showPassword()) {
1375
1418
  <ng-container [ngTemplateOutlet]="eyeOffIcon"></ng-container>
1376
1419
  } @else {
1377
1420
  <ng-container [ngTemplateOutlet]="eyeIcon"></ng-container>
1378
1421
  }
1379
1422
  </button>
1423
+ } @else if (type() !== 'password') {
1424
+ @if (suffix() != null) {
1425
+ <span class="nmf-suffix">
1426
+ {{ suffix() }}
1427
+ </span>
1428
+ }
1429
+ <ng-content select="[nmfSuffix]" />
1380
1430
  }
1381
1431
  </div>
1382
1432
  </nmf-form-field>
@@ -1436,25 +1486,31 @@ class InputTextareaComponent extends FormControlBase {
1436
1486
  [loading]="loading()"
1437
1487
  [errorMessage]="errorMessage()"
1438
1488
  >
1439
- <textarea
1440
- #focusable
1441
- class="nmf-input"
1442
- [ngClass]="classList()"
1489
+ <div
1490
+ class="nmf-control-wrapper nmf-textarea"
1443
1491
  [class.error]="hasErrors()"
1444
1492
  [class.disabled]="disabled()"
1445
- [id]="id()"
1446
- [rows]="rows()"
1447
- [cols]="cols()"
1448
- [value]="value"
1449
- [disabled]="disabled()"
1450
- [required]="isRequired()"
1451
- [placeholder]="placeholder()"
1452
- [autocomplete]="autocompleteAttr()"
1453
- (blur)="onTouched()"
1454
- (input)="onInput($event)"
1455
- ></textarea>
1493
+ >
1494
+ <textarea
1495
+ #focusable
1496
+ class="nmf-control"
1497
+ [ngClass]="classList()"
1498
+ [class.error]="hasErrors()"
1499
+ [class.disabled]="disabled()"
1500
+ [id]="id()"
1501
+ [rows]="rows()"
1502
+ [cols]="cols()"
1503
+ [value]="value"
1504
+ [disabled]="disabled()"
1505
+ [required]="isRequired()"
1506
+ [placeholder]="placeholder()"
1507
+ [autocomplete]="autocompleteAttr()"
1508
+ (blur)="onTouched()"
1509
+ (input)="onInput($event)"
1510
+ ></textarea>
1511
+ </div>
1456
1512
  </nmf-form-field>
1457
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "isRequired", "loading", "hint", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1513
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "loading", "hint", "isRequired", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1458
1514
  }
1459
1515
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InputTextareaComponent, decorators: [{
1460
1516
  type: Component,
@@ -1470,23 +1526,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1470
1526
  [loading]="loading()"
1471
1527
  [errorMessage]="errorMessage()"
1472
1528
  >
1473
- <textarea
1474
- #focusable
1475
- class="nmf-input"
1476
- [ngClass]="classList()"
1529
+ <div
1530
+ class="nmf-control-wrapper nmf-textarea"
1477
1531
  [class.error]="hasErrors()"
1478
1532
  [class.disabled]="disabled()"
1479
- [id]="id()"
1480
- [rows]="rows()"
1481
- [cols]="cols()"
1482
- [value]="value"
1483
- [disabled]="disabled()"
1484
- [required]="isRequired()"
1485
- [placeholder]="placeholder()"
1486
- [autocomplete]="autocompleteAttr()"
1487
- (blur)="onTouched()"
1488
- (input)="onInput($event)"
1489
- ></textarea>
1533
+ >
1534
+ <textarea
1535
+ #focusable
1536
+ class="nmf-control"
1537
+ [ngClass]="classList()"
1538
+ [class.error]="hasErrors()"
1539
+ [class.disabled]="disabled()"
1540
+ [id]="id()"
1541
+ [rows]="rows()"
1542
+ [cols]="cols()"
1543
+ [value]="value"
1544
+ [disabled]="disabled()"
1545
+ [required]="isRequired()"
1546
+ [placeholder]="placeholder()"
1547
+ [autocomplete]="autocompleteAttr()"
1548
+ (blur)="onTouched()"
1549
+ (input)="onInput($event)"
1550
+ ></textarea>
1551
+ </div>
1490
1552
  </nmf-form-field>
1491
1553
  `,
1492
1554
  }]
@@ -1532,26 +1594,30 @@ class InputTimepickerComponent extends FormControlBase {
1532
1594
  [loading]="loading()"
1533
1595
  [errorMessage]="errorMessage()"
1534
1596
  >
1535
- <input
1536
- #focusable
1537
- type="time"
1538
- class="nmf-input"
1539
- [ngClass]="classList()"
1597
+ <div
1598
+ class="nmf-control-wrapper"
1540
1599
  [class.error]="hasErrors()"
1541
1600
  [class.disabled]="disabled()"
1542
- [id]="id()"
1543
- [name]="name()"
1544
- [step]="step()"
1545
- [value]="displayValue()"
1546
- [disabled]="disabled()"
1547
- [required]="isRequired()"
1548
- [placeholder]="placeholder()"
1549
- [autocomplete]="autocompleteAttr()"
1550
- (blur)="onTouched()"
1551
- (input)="onInput($event)"
1552
- />
1601
+ >
1602
+ <input
1603
+ #focusable
1604
+ type="time"
1605
+ class="nmf-control"
1606
+ [ngClass]="classList()"
1607
+ [id]="id()"
1608
+ [name]="name()"
1609
+ [step]="step()"
1610
+ [value]="displayValue()"
1611
+ [disabled]="disabled()"
1612
+ [required]="isRequired()"
1613
+ [placeholder]="placeholder()"
1614
+ [autocomplete]="autocompleteAttr()"
1615
+ (blur)="onTouched()"
1616
+ (input)="onInput($event)"
1617
+ />
1618
+ </div>
1553
1619
  </nmf-form-field>
1554
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "isRequired", "loading", "hint", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1620
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "nmf-form-field", inputs: ["label", "loading", "hint", "isRequired", "errorMessage"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1555
1621
  }
1556
1622
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: InputTimepickerComponent, decorators: [{
1557
1623
  type: Component,
@@ -1567,24 +1633,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1567
1633
  [loading]="loading()"
1568
1634
  [errorMessage]="errorMessage()"
1569
1635
  >
1570
- <input
1571
- #focusable
1572
- type="time"
1573
- class="nmf-input"
1574
- [ngClass]="classList()"
1636
+ <div
1637
+ class="nmf-control-wrapper"
1575
1638
  [class.error]="hasErrors()"
1576
1639
  [class.disabled]="disabled()"
1577
- [id]="id()"
1578
- [name]="name()"
1579
- [step]="step()"
1580
- [value]="displayValue()"
1581
- [disabled]="disabled()"
1582
- [required]="isRequired()"
1583
- [placeholder]="placeholder()"
1584
- [autocomplete]="autocompleteAttr()"
1585
- (blur)="onTouched()"
1586
- (input)="onInput($event)"
1587
- />
1640
+ >
1641
+ <input
1642
+ #focusable
1643
+ type="time"
1644
+ class="nmf-control"
1645
+ [ngClass]="classList()"
1646
+ [id]="id()"
1647
+ [name]="name()"
1648
+ [step]="step()"
1649
+ [value]="displayValue()"
1650
+ [disabled]="disabled()"
1651
+ [required]="isRequired()"
1652
+ [placeholder]="placeholder()"
1653
+ [autocomplete]="autocompleteAttr()"
1654
+ (blur)="onTouched()"
1655
+ (input)="onInput($event)"
1656
+ />
1657
+ </div>
1588
1658
  </nmf-form-field>
1589
1659
  `,
1590
1660
  }]
@@ -1596,11 +1666,20 @@ function isRecord(value) {
1596
1666
 
1597
1667
  class FormHydrator {
1598
1668
  hydrate(form, model, registry = {}) {
1669
+ if (form instanceof FormArray) {
1670
+ this.hydrateFormArray(form, model, registry);
1671
+ return;
1672
+ }
1599
1673
  Object.entries(form.controls).forEach(([key, control]) => {
1600
- if (!(key in model))
1674
+ if (!(key in model)) {
1601
1675
  return;
1676
+ }
1602
1677
  const mapFn = registry[key]?.fromModel;
1603
- const value = model?.[key];
1678
+ const value = model[key];
1679
+ if (control instanceof FormArray) {
1680
+ this.hydrateFormArray(control, value, registry);
1681
+ return;
1682
+ }
1604
1683
  if (control instanceof FormGroup) {
1605
1684
  if (mapFn) {
1606
1685
  const mapped = mapFn(value);
@@ -1609,13 +1688,70 @@ class FormHydrator {
1609
1688
  }
1610
1689
  }
1611
1690
  else {
1612
- this.hydrate(control, (value ?? {}), registry);
1691
+ this.hydrate(control, value ?? {}, registry);
1613
1692
  }
1614
1693
  return;
1615
1694
  }
1616
1695
  control.patchValue(value, { emitEvent: false });
1617
1696
  });
1618
1697
  }
1698
+ hydrateFormArray(formArray, model, registry) {
1699
+ const values = Array.isArray(model) ? model : [];
1700
+ if (values.length === 0) {
1701
+ formArray.clear();
1702
+ return;
1703
+ }
1704
+ if (formArray.length === 0) {
1705
+ formArray.push(this.createControlFromModel(values[0]));
1706
+ }
1707
+ while (formArray.length > values.length) {
1708
+ formArray.removeAt(formArray.length - 1);
1709
+ }
1710
+ while (formArray.length < values.length) {
1711
+ const template = formArray.at(formArray.length - 1);
1712
+ formArray.push(this.cloneControl(template));
1713
+ }
1714
+ values.forEach((item, index) => {
1715
+ const child = formArray.at(index);
1716
+ if (child instanceof FormGroup && isRecord(item)) {
1717
+ this.hydrate(child, item, registry);
1718
+ }
1719
+ else if (child instanceof FormArray && Array.isArray(item)) {
1720
+ this.hydrate(child, item, registry);
1721
+ }
1722
+ else {
1723
+ child.patchValue(item, { emitEvent: false });
1724
+ }
1725
+ });
1726
+ }
1727
+ createControlFromModel(value) {
1728
+ if (Array.isArray(value)) {
1729
+ return new FormArray(value.map((item) => this.createControlFromModel(item)));
1730
+ }
1731
+ if (isRecord(value)) {
1732
+ return new FormGroup(Object.fromEntries(Object.keys(value).map((key) => [key, new FormControl(null)])));
1733
+ }
1734
+ return new FormControl(null);
1735
+ }
1736
+ cloneControl(control) {
1737
+ if (control instanceof FormControl) {
1738
+ return new FormControl(null, control.validator, control.asyncValidator);
1739
+ }
1740
+ if (control instanceof FormGroup) {
1741
+ return new FormGroup(Object.fromEntries(Object.entries(control.controls).map(([key, child]) => [
1742
+ key,
1743
+ this.cloneControl(child),
1744
+ ])), {
1745
+ validators: control.validator,
1746
+ asyncValidators: control.asyncValidator,
1747
+ updateOn: control.updateOn,
1748
+ });
1749
+ }
1750
+ return new FormArray(control.controls.map((child) => this.cloneControl(child)), {
1751
+ validators: control.validator,
1752
+ asyncValidators: control.asyncValidator,
1753
+ });
1754
+ }
1619
1755
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: FormHydrator, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1620
1756
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: FormHydrator, providedIn: 'root' });
1621
1757
  }
@@ -1626,16 +1762,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1626
1762
 
1627
1763
  class FormSerializer {
1628
1764
  toRequest(form, registry = {}, options) {
1765
+ if (form instanceof FormArray) {
1766
+ return form.controls.map((control) => {
1767
+ if (control instanceof FormControl) {
1768
+ return control.value;
1769
+ }
1770
+ if (control instanceof FormGroup) {
1771
+ return this.toRequest(control, registry, options);
1772
+ }
1773
+ if (control instanceof FormArray) {
1774
+ return this.toRequest(control, registry, options);
1775
+ }
1776
+ return null;
1777
+ });
1778
+ }
1629
1779
  const result = {};
1630
1780
  Object.entries(form.controls).forEach(([key, control]) => {
1631
1781
  const mapFn = registry[key]?.toRequest;
1782
+ if (control instanceof FormControl) {
1783
+ result[key] = control.value;
1784
+ return;
1785
+ }
1632
1786
  if (control instanceof FormGroup) {
1633
1787
  result[key] = mapFn
1634
1788
  ? mapFn(control.value, options)
1635
1789
  : this.toRequest(control, registry, options);
1636
1790
  return;
1637
1791
  }
1638
- result[key] = control.value;
1792
+ if (control instanceof FormArray) {
1793
+ result[key] = mapFn
1794
+ ? mapFn(control.value, options)
1795
+ : this.toRequest(control, registry, options);
1796
+ return;
1797
+ }
1639
1798
  });
1640
1799
  return result;
1641
1800
  }
@@ -1687,6 +1846,7 @@ class FormOrchestrator extends FormMapperBase {
1687
1846
  }
1688
1847
  addHandlerToRegistry(handler) {
1689
1848
  this._handlerRegistry.set([...this._handlerRegistry(), handler]);
1849
+ this.addReactiveLogic(handler.getReactiveLogic(this.form()));
1690
1850
  }
1691
1851
  addReactiveLogic(subscription) {
1692
1852
  this._logicSubscription.add(subscription);
@@ -1705,10 +1865,15 @@ class FormOrchestrator extends FormMapperBase {
1705
1865
  return;
1706
1866
  const mapper = registry[key];
1707
1867
  const value = model[key];
1868
+ if (control instanceof FormControl) {
1869
+ const mapped = mapper ? mapper.fromModel(value) : value;
1870
+ control.setValue(mapped, { emitEvent: false });
1871
+ return;
1872
+ }
1708
1873
  if (control instanceof FormGroup) {
1709
1874
  const mapped = mapper ? mapper.fromModel(value) : value;
1710
1875
  if (isRecord(mapped)) {
1711
- this.hydrator.hydrate(control, mapped);
1876
+ this.hydrator.hydrate(control, mapped, registry);
1712
1877
  }
1713
1878
  }
1714
1879
  });
@@ -1734,5 +1899,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
1734
1899
  * Generated bundle index. Do not edit.
1735
1900
  */
1736
1901
 
1737
- export { CurrencyBehavior, FormControlBase, FormHandlerBase, FormHydrator, FormMapperBase, FormOrchestrator, FormSerializer, InputCurrencyComponent, InputDatepickerComponent, InputLookupComponent, InputNumberComponent, InputSelectComponent, InputTextComponent, InputTextareaComponent, InputTimepickerComponent, LookupBehavior, TextBehavior, formatNumber, getControl, getControlValue, parseNumber };
1902
+ export { FormControlBase, FormFieldComponent, FormHandlerBase, FormHydrator, FormMapperBase, FormOrchestrator, FormSerializer, InputDatepickerComponent, InputLookupComponent, InputNumberComponent, InputSelectComponent, InputTextComponent, InputTextareaComponent, InputTimepickerComponent, LookupBehavior, NmfPrefixDirective, NmfSuffixDirective, NumberBehavior, PasswordBehavior, formatNumber, getControl, getControlValue, parseNumber };
1738
1903
  //# sourceMappingURL=ng-modular-forms-core.mjs.map