@praxisui/dynamic-fields 8.0.0-beta.24 → 8.0.0-beta.25

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.
@@ -752,6 +752,161 @@ function resolvePraxisDynamicFieldsText(value, fallback) {
752
752
  return { text: fallback ?? '' };
753
753
  }
754
754
 
755
+ function resolveInlineDisplayMask(metadata, currentValue) {
756
+ const explicitMask = stringValue(metadata['displayMask']) ||
757
+ stringValue(metadata['mask']) ||
758
+ stringValue(metadata['inputMask']) ||
759
+ maskFromFormat(metadata['format']);
760
+ if (explicitMask) {
761
+ if (hasPhoneSignal(metadata, buildComparableSignals(metadata))) {
762
+ const digits = digitsOnly(currentValue);
763
+ if (digits.startsWith('55') && digits.length > 11 && explicitMask.includes('+55')) {
764
+ return {
765
+ mask: resolveBrazilianPhoneMask(currentValue),
766
+ rawMode: 'digits',
767
+ };
768
+ }
769
+ }
770
+ return {
771
+ mask: explicitMask,
772
+ rawMode: /[Xx]/.test(explicitMask) ? 'alphanumeric' : 'digits',
773
+ };
774
+ }
775
+ const documentType = normalizeComparableToken(metadata['documentType'] ??
776
+ metadata['documentKind'] ??
777
+ metadata['identifierType'] ??
778
+ metadata['identifierKind']);
779
+ if (documentType === 'cpf') {
780
+ return { mask: '000.000.000-00', rawMode: 'digits' };
781
+ }
782
+ if (documentType === 'cnpj') {
783
+ return { mask: '00.000.000/0000-00', rawMode: 'digits' };
784
+ }
785
+ const signals = buildComparableSignals(metadata);
786
+ if (hasSignal(signals, 'cpfcnpj')) {
787
+ const digits = digitsOnly(currentValue);
788
+ return {
789
+ mask: digits.length > 11 ? '00.000.000/0000-00' : '000.000.000-00',
790
+ rawMode: 'digits',
791
+ };
792
+ }
793
+ if (hasSignal(signals, 'cpf')) {
794
+ return { mask: '000.000.000-00', rawMode: 'digits' };
795
+ }
796
+ if (hasSignal(signals, 'cnpj')) {
797
+ return { mask: '00.000.000/0000-00', rawMode: 'digits' };
798
+ }
799
+ if (hasPhoneSignal(metadata, signals)) {
800
+ return {
801
+ mask: resolveBrazilianPhoneMask(currentValue),
802
+ rawMode: 'digits',
803
+ };
804
+ }
805
+ if (hasPostalCodeSignal(signals)) {
806
+ return { mask: '00000-000', rawMode: 'digits' };
807
+ }
808
+ return null;
809
+ }
810
+ function applyInlineDisplayMask(value, mask) {
811
+ if (!value) {
812
+ return '';
813
+ }
814
+ let valueIndex = 0;
815
+ let output = '';
816
+ for (const token of mask.mask) {
817
+ if (valueIndex >= value.length) {
818
+ break;
819
+ }
820
+ if (isMaskPlaceholder(token)) {
821
+ output += value[valueIndex++] ?? '';
822
+ }
823
+ else {
824
+ output += token;
825
+ }
826
+ }
827
+ return output;
828
+ }
829
+ function unmaskInlineDisplayValue(value, mask) {
830
+ const text = String(value ?? '');
831
+ return mask.rawMode === 'alphanumeric'
832
+ ? text.replace(/[^a-zA-Z0-9]/g, '')
833
+ : digitsOnly(text);
834
+ }
835
+ function resolveBrazilianPhoneMask(value) {
836
+ const digits = digitsOnly(value);
837
+ if (digits.startsWith('55') && digits.length > 11) {
838
+ return '+00 00 00000-0000';
839
+ }
840
+ return digits.length <= 10 ? '(00) 0000-0000' : '(00) 00000-0000';
841
+ }
842
+ function hasPhoneSignal(metadata, signals) {
843
+ const phoneFormat = normalizeComparableToken(metadata['phoneFormat']);
844
+ const defaultCountry = normalizeComparableToken(metadata['defaultCountry']);
845
+ return (phoneFormat === 'national' ||
846
+ phoneFormat === 'international' ||
847
+ phoneFormat === 'e164' ||
848
+ defaultCountry === 'br' ||
849
+ hasSignal(signals, 'phone') ||
850
+ hasSignal(signals, 'telefone') ||
851
+ hasSignal(signals, 'celular') ||
852
+ hasSignal(signals, 'mobile') ||
853
+ hasSignal(signals, 'whatsapp') ||
854
+ hasSignal(signals, 'tel'));
855
+ }
856
+ function hasPostalCodeSignal(signals) {
857
+ return (hasSignal(signals, 'cep') ||
858
+ hasSignal(signals, 'postalcode') ||
859
+ hasSignal(signals, 'zipcode') ||
860
+ hasSignal(signals, 'zip'));
861
+ }
862
+ function buildComparableSignals(metadata) {
863
+ return [
864
+ metadata['name'],
865
+ metadata['id'],
866
+ metadata['key'],
867
+ metadata['label'],
868
+ metadata['placeholder'],
869
+ metadata['controlType'],
870
+ metadata['type'],
871
+ metadata['dataType'],
872
+ metadata['format'],
873
+ metadata['mask'],
874
+ metadata['inputMask'],
875
+ ]
876
+ .map(normalizeComparableToken)
877
+ .filter(Boolean)
878
+ .join('|');
879
+ }
880
+ function hasSignal(signals, token) {
881
+ return signals.split('|').includes(token) || signals.includes(token);
882
+ }
883
+ function maskFromFormat(value) {
884
+ const format = stringValue(value);
885
+ if (!format) {
886
+ return null;
887
+ }
888
+ const hasMaskTokens = /[0#9Xx]/.test(format);
889
+ const hasInvalidLetters = /[A-WYZawyz]/.test(format);
890
+ return hasMaskTokens && !hasInvalidLetters ? format : null;
891
+ }
892
+ function stringValue(value) {
893
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
894
+ }
895
+ function normalizeComparableToken(value) {
896
+ return String(value ?? '')
897
+ .trim()
898
+ .toLowerCase()
899
+ .normalize('NFD')
900
+ .replace(/[\u0300-\u036f]/g, '')
901
+ .replace(/[\s_-]+/g, '');
902
+ }
903
+ function digitsOnly(value) {
904
+ return String(value ?? '').replace(/\D/g, '');
905
+ }
906
+ function isMaskPlaceholder(token) {
907
+ return token === '0' || token === '9' || token === '#' || token === 'X' || token === 'x';
908
+ }
909
+
755
910
  /**
756
911
  * @fileoverview Simple base component for input fields with basic ControlValueAccessor functionality
757
912
  *
@@ -1375,6 +1530,7 @@ class SimpleBaseInputComponent {
1375
1530
  this.syncInProgress = false;
1376
1531
  }
1377
1532
  }
1533
+ queueMicrotask(() => this.applyNativeDisplayMask());
1378
1534
  }
1379
1535
  registerOnChange(fn) {
1380
1536
  this.onChange = fn;
@@ -1405,6 +1561,9 @@ class SimpleBaseInputComponent {
1405
1561
  // =============================================================================
1406
1562
  handleInput(event) {
1407
1563
  const input = event.target;
1564
+ if (this.handleMaskedNativeInput(input)) {
1565
+ return;
1566
+ }
1408
1567
  const value = input.value;
1409
1568
  if (!this.syncInProgress) {
1410
1569
  this.syncInProgress = true;
@@ -1718,6 +1877,7 @@ class SimpleBaseInputComponent {
1718
1877
  this.nativeElement = el;
1719
1878
  this.attachNativeEventHandlers();
1720
1879
  this.applyNativeAttributes();
1880
+ this.applyNativeDisplayMask();
1721
1881
  this.log('debug', 'registerInputElement: element attached and attributes applied');
1722
1882
  }
1723
1883
  /** Aplica atributos e ARIA com base no metadata */
@@ -1903,6 +2063,7 @@ class SimpleBaseInputComponent {
1903
2063
  .subscribe((value) => {
1904
2064
  this.fieldState.update((state) => ({ ...state, value }));
1905
2065
  this.syncComponentStateFromControl(control);
2066
+ queueMicrotask(() => this.applyNativeDisplayMask(value));
1906
2067
  });
1907
2068
  control.statusChanges
1908
2069
  .pipe(takeUntilDestroyed(this.destroyRef))
@@ -1942,6 +2103,59 @@ class SimpleBaseInputComponent {
1942
2103
  disabled: !!control.disabled,
1943
2104
  }));
1944
2105
  }
2106
+ handleMaskedNativeInput(input) {
2107
+ if (!input) {
2108
+ return false;
2109
+ }
2110
+ const mask = this.resolveNativeDisplayMask(input.value);
2111
+ if (!mask) {
2112
+ return false;
2113
+ }
2114
+ const rawValue = unmaskInlineDisplayValue(input.value, mask);
2115
+ const displayValue = applyInlineDisplayMask(rawValue, mask);
2116
+ if (!this.syncInProgress) {
2117
+ this.syncInProgress = true;
2118
+ try {
2119
+ const control = this.control();
2120
+ control.setValue(rawValue, { emitEvent: false });
2121
+ this.onChange(rawValue);
2122
+ this.valueChange.emit(rawValue);
2123
+ this.fieldState.update((state) => ({ ...state, value: rawValue }));
2124
+ this.markAsDirty();
2125
+ this.applyErrorDebounceOnChange();
2126
+ }
2127
+ finally {
2128
+ this.syncInProgress = false;
2129
+ }
2130
+ }
2131
+ queueMicrotask(() => {
2132
+ if (input.value !== displayValue) {
2133
+ input.value = displayValue;
2134
+ }
2135
+ });
2136
+ return true;
2137
+ }
2138
+ applyNativeDisplayMask(value = this.control().value) {
2139
+ const input = this.nativeElement;
2140
+ if (!input || input.tagName.toLowerCase() !== 'input') {
2141
+ return;
2142
+ }
2143
+ const mask = this.resolveNativeDisplayMask(value);
2144
+ if (!mask) {
2145
+ return;
2146
+ }
2147
+ const displayValue = applyInlineDisplayMask(String(value ?? ''), mask);
2148
+ if (input.value !== displayValue) {
2149
+ input.value = displayValue;
2150
+ }
2151
+ }
2152
+ resolveNativeDisplayMask(value) {
2153
+ const metadata = this.metadata();
2154
+ if (!metadata) {
2155
+ return null;
2156
+ }
2157
+ return resolveInlineDisplayMask(metadata, value);
2158
+ }
1945
2159
  // Atualiza tooltip/atributos de erro quando erros mudam e quando inline errors estiverem desabilitados
1946
2160
  refreshErrorTooltip() {
1947
2161
  if (!this.nativeElement)
@@ -2947,7 +3161,9 @@ class SimpleBaseSelectComponent extends SimpleBaseInputComponent {
2947
3161
  this.emptyOptionText.set(isMultiple ? null : (metadata.emptyOptionText ?? fallbackEmptyOption));
2948
3162
  const optionSource = metadata.optionSource ?? null;
2949
3163
  this.optionSource.set(optionSource);
2950
- this.useOptionsFilterEndpoint.set(this.isOptionsFilterEndpoint(metadata.endpoint));
3164
+ this.useOptionsFilterEndpoint.set(this.isOptionsFilterEndpoint(metadata.endpoint) ||
3165
+ this.isOptionsFilterEndpoint(metadata.optionsEndpoint) ||
3166
+ String(metadata.optionsEndpoint ?? '').trim().toLowerCase() === 'filter');
2951
3167
  const path = optionSource?.resourcePath ?? metadata.resourcePath ?? metadata.endpoint;
2952
3168
  if (path) {
2953
3169
  this.resourcePath.set(path);
@@ -3038,8 +3254,10 @@ class SimpleBaseSelectComponent extends SimpleBaseInputComponent {
3038
3254
  const meta = this.metadata();
3039
3255
  if (!meta)
3040
3256
  return;
3041
- if (meta.compareWith)
3042
- this.matSelect.compareWith = meta.compareWith;
3257
+ this.matSelect.compareWith =
3258
+ typeof meta.compareWith === 'function'
3259
+ ? meta.compareWith
3260
+ : (left, right) => this.areOptionValuesEqual(left, right);
3043
3261
  // MatSelect does not support a displayWith property; formatting should
3044
3262
  // be handled via option labels or mat-select-trigger in templates.
3045
3263
  const panelClass = this.selectPanelClass();
@@ -3378,7 +3596,16 @@ class SimpleBaseSelectComponent extends SimpleBaseInputComponent {
3378
3596
  return currentCount >= max;
3379
3597
  }
3380
3598
  areOptionValuesEqual(left, right) {
3381
- return left === right;
3599
+ return this.normalizeComparableOptionValue(left) === this.normalizeComparableOptionValue(right);
3600
+ }
3601
+ normalizeComparableOptionValue(value) {
3602
+ if (value && typeof value === 'object' && 'id' in value) {
3603
+ return this.normalizeComparableOptionValue(value.id);
3604
+ }
3605
+ if (typeof value === 'string' || typeof value === 'number') {
3606
+ return String(value).trim();
3607
+ }
3608
+ return value;
3382
3609
  }
3383
3610
  /**
3384
3611
  * Toggles selection of all options when `selectAll` is enabled in multiple
@@ -3476,6 +3703,10 @@ class SimpleBaseSelectComponent extends SimpleBaseInputComponent {
3476
3703
  if (left === right) {
3477
3704
  return true;
3478
3705
  }
3706
+ if (this.normalizeComparableOptionValue(left) ===
3707
+ this.normalizeComparableOptionValue(right)) {
3708
+ return true;
3709
+ }
3479
3710
  try {
3480
3711
  return JSON.stringify(left) === JSON.stringify(right);
3481
3712
  }
@@ -11444,10 +11675,10 @@ class MaterialAsyncSelectComponent extends SimpleBaseSelectComponent {
11444
11675
  request$
11445
11676
  .pipe(takeUntilDestroyed(this.destroyRef))
11446
11677
  .subscribe((opts) => {
11447
- const returned = new Set(opts.map((o) => o.id));
11678
+ const returned = new Set(opts.map((o) => this.normalizeComparableOptionValue(o.id)));
11448
11679
  opts.forEach((o) => this.store.upsertById(o));
11449
11680
  ids
11450
- .filter((id) => !returned.has(id))
11681
+ .filter((id) => !returned.has(this.normalizeComparableOptionValue(id)))
11451
11682
  .forEach((id) => this.store.upsertById({ id, label: this.missingOptionLabel(id) }, true));
11452
11683
  this.store.ensureVisible(ids);
11453
11684
  this.refreshOptions();
@@ -11748,7 +11979,8 @@ class MaterialAsyncSelectComponent extends SimpleBaseSelectComponent {
11748
11979
  return option.extra?.['selectable'] === false;
11749
11980
  }
11750
11981
  areOptionValuesEqual(left, right) {
11751
- return this.normalizeRemoteOptionValue(left) === this.normalizeRemoteOptionValue(right);
11982
+ return (this.normalizeComparableOptionValue(this.normalizeRemoteOptionValue(left)) ===
11983
+ this.normalizeComparableOptionValue(this.normalizeRemoteOptionValue(right)));
11752
11984
  }
11753
11985
  normalizeRemoteOptionValue(value) {
11754
11986
  if (value && typeof value === 'object' && 'id' in value) {
@@ -16039,6 +16271,8 @@ class MaterialSelectComponent extends SimpleBaseSelectComponent {
16039
16271
  this.devWarnSelectAliases(matMetadata, 'MaterialSelectComponent');
16040
16272
  const source = matMetadata.selectOptions ?? this.optionsFromLegacy(matMetadata);
16041
16273
  const mappedOptions = source?.map((option) => this.normalizeOption(option));
16274
+ const optionLabelKey = matMetadata.optionLabelKey ?? matMetadata.displayField;
16275
+ const optionValueKey = matMetadata.optionValueKey ?? matMetadata.valueField;
16042
16276
  super.setSelectMetadata({
16043
16277
  ...matMetadata,
16044
16278
  options: mappedOptions,
@@ -16046,8 +16280,8 @@ class MaterialSelectComponent extends SimpleBaseSelectComponent {
16046
16280
  searchable: matMetadata.searchable,
16047
16281
  resourcePath: matMetadata.resourcePath,
16048
16282
  filterCriteria: matMetadata.filterCriteria,
16049
- optionLabelKey: matMetadata.optionLabelKey,
16050
- optionValueKey: matMetadata.optionValueKey,
16283
+ optionLabelKey,
16284
+ optionValueKey,
16051
16285
  });
16052
16286
  }
16053
16287
  ngOnInit() {
@@ -16071,9 +16305,10 @@ class MaterialSelectComponent extends SimpleBaseSelectComponent {
16071
16305
  }
16072
16306
  normalizeOption(option) {
16073
16307
  const label = option['label'] ?? option['text'];
16308
+ const value = option['value'] ?? option['id'];
16074
16309
  return {
16075
16310
  label: typeof label === 'string' ? label : String(label ?? ''),
16076
- value: option['value'],
16311
+ value,
16077
16312
  disabled: option['disabled'] === true ? true : undefined,
16078
16313
  };
16079
16314
  }
@@ -17226,6 +17461,9 @@ class PhoneInputComponent extends SimpleBaseInputComponent {
17226
17461
  super.registerInputElement(el);
17227
17462
  this.applyPhoneDisplayValue();
17228
17463
  }
17464
+ applyNativeDisplayMask() {
17465
+ this.applyPhoneDisplayValue();
17466
+ }
17229
17467
  handleInput(event) {
17230
17468
  if (!this.isPhoneRuntimeEnabled()) {
17231
17469
  super.handleInput(event);
@@ -20456,6 +20694,11 @@ class MaterialCurrencyComponent extends SimpleBaseInputComponent {
20456
20694
  this.setCaretToEnd();
20457
20695
  }
20458
20696
  }
20697
+ applyNativeDisplayMask() {
20698
+ if (this.inputRef?.nativeElement) {
20699
+ this.formatDisplayValue();
20700
+ }
20701
+ }
20459
20702
  async validateField() {
20460
20703
  const errors = await super.validateField();
20461
20704
  this.validationChange.emit(errors);
@@ -20733,14 +20976,11 @@ class MaterialCurrencyComponent extends SimpleBaseInputComponent {
20733
20976
  </mat-hint>
20734
20977
  }
20735
20978
  </mat-form-field>
20736
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i2.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
20979
+ `, isInline: true, styles: [":host ::ng-deep .pdx-material-currency .mat-mdc-form-field-icon-prefix,:host ::ng-deep .pdx-material-currency .mat-mdc-form-field-text-prefix,:host ::ng-deep .pdx-material-currency .mat-mdc-form-field-icon-suffix,:host ::ng-deep .pdx-material-currency .mat-mdc-form-field-text-suffix{align-self:center;display:inline-flex;align-items:center}.currency-symbol{display:inline-flex;align-items:center;line-height:1;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i2.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
20737
20980
  }
20738
20981
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MaterialCurrencyComponent, decorators: [{
20739
20982
  type: Component,
20740
- args: [{
20741
- selector: 'pdx-material-currency',
20742
- standalone: true,
20743
- template: `
20983
+ args: [{ selector: 'pdx-material-currency', standalone: true, template: `
20744
20984
  <mat-form-field
20745
20985
  [appearance]="materialAppearance()"
20746
20986
  [color]="materialColor()"
@@ -20830,8 +21070,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20830
21070
  </mat-hint>
20831
21071
  }
20832
21072
  </mat-form-field>
20833
- `,
20834
- imports: [
21073
+ `, imports: [
20835
21074
  MatButtonModule,
20836
21075
  CommonModule,
20837
21076
  MatFormFieldModule,
@@ -20840,16 +21079,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20840
21079
  MatTooltipModule,
20841
21080
  PraxisIconDirective,
20842
21081
  ReactiveFormsModule,
20843
- ],
20844
- providers: [
21082
+ ], providers: [
20845
21083
  CurrencyPipe,
20846
21084
  {
20847
21085
  provide: NG_VALUE_ACCESSOR,
20848
21086
  useExisting: forwardRef(() => MaterialCurrencyComponent),
20849
21087
  multi: true,
20850
21088
  },
20851
- ],
20852
- host: {
21089
+ ], host: {
20853
21090
  '[class]': 'componentCssClasses()',
20854
21091
  '[class.praxis-disabled]': 'disabledMode',
20855
21092
  '[style.display]': 'visible ? null : "none"',
@@ -20857,8 +21094,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20857
21094
  '[attr.data-field-type]': '"currency"',
20858
21095
  '[attr.data-field-name]': 'metadata()?.name',
20859
21096
  '[attr.data-component-id]': 'componentId()',
20860
- },
20861
- }]
21097
+ }, styles: [":host ::ng-deep .pdx-material-currency .mat-mdc-form-field-icon-prefix,:host ::ng-deep .pdx-material-currency .mat-mdc-form-field-text-prefix,:host ::ng-deep .pdx-material-currency .mat-mdc-form-field-icon-suffix,:host ::ng-deep .pdx-material-currency .mat-mdc-form-field-text-suffix{align-self:center;display:inline-flex;align-items:center}.currency-symbol{display:inline-flex;align-items:center;line-height:1;white-space:nowrap}\n"] }]
20862
21098
  }], propDecorators: { validationChange: [{ type: i0.Output, args: ["validationChange"] }], inputRef: [{
20863
21099
  type: ViewChild,
20864
21100
  args: ['currencyInput', { static: true }]
@@ -25369,161 +25605,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
25369
25605
  args: ['window:resize']
25370
25606
  }] } });
25371
25607
 
25372
- function resolveInlineDisplayMask(metadata, currentValue) {
25373
- const explicitMask = stringValue(metadata['displayMask']) ||
25374
- stringValue(metadata['mask']) ||
25375
- stringValue(metadata['inputMask']) ||
25376
- maskFromFormat(metadata['format']);
25377
- if (explicitMask) {
25378
- if (hasPhoneSignal(metadata, buildComparableSignals(metadata))) {
25379
- const digits = digitsOnly(currentValue);
25380
- if (digits.startsWith('55') && digits.length > 11 && explicitMask.includes('+55')) {
25381
- return {
25382
- mask: resolveBrazilianPhoneMask(currentValue),
25383
- rawMode: 'digits',
25384
- };
25385
- }
25386
- }
25387
- return {
25388
- mask: explicitMask,
25389
- rawMode: /[Xx]/.test(explicitMask) ? 'alphanumeric' : 'digits',
25390
- };
25391
- }
25392
- const documentType = normalizeComparableToken(metadata['documentType'] ??
25393
- metadata['documentKind'] ??
25394
- metadata['identifierType'] ??
25395
- metadata['identifierKind']);
25396
- if (documentType === 'cpf') {
25397
- return { mask: '000.000.000-00', rawMode: 'digits' };
25398
- }
25399
- if (documentType === 'cnpj') {
25400
- return { mask: '00.000.000/0000-00', rawMode: 'digits' };
25401
- }
25402
- const signals = buildComparableSignals(metadata);
25403
- if (hasSignal(signals, 'cpfcnpj')) {
25404
- const digits = digitsOnly(currentValue);
25405
- return {
25406
- mask: digits.length > 11 ? '00.000.000/0000-00' : '000.000.000-00',
25407
- rawMode: 'digits',
25408
- };
25409
- }
25410
- if (hasSignal(signals, 'cpf')) {
25411
- return { mask: '000.000.000-00', rawMode: 'digits' };
25412
- }
25413
- if (hasSignal(signals, 'cnpj')) {
25414
- return { mask: '00.000.000/0000-00', rawMode: 'digits' };
25415
- }
25416
- if (hasPhoneSignal(metadata, signals)) {
25417
- return {
25418
- mask: resolveBrazilianPhoneMask(currentValue),
25419
- rawMode: 'digits',
25420
- };
25421
- }
25422
- if (hasPostalCodeSignal(signals)) {
25423
- return { mask: '00000-000', rawMode: 'digits' };
25424
- }
25425
- return null;
25426
- }
25427
- function applyInlineDisplayMask(value, mask) {
25428
- if (!value) {
25429
- return '';
25430
- }
25431
- let valueIndex = 0;
25432
- let output = '';
25433
- for (const token of mask.mask) {
25434
- if (valueIndex >= value.length) {
25435
- break;
25436
- }
25437
- if (isMaskPlaceholder(token)) {
25438
- output += value[valueIndex++] ?? '';
25439
- }
25440
- else {
25441
- output += token;
25442
- }
25443
- }
25444
- return output;
25445
- }
25446
- function unmaskInlineDisplayValue(value, mask) {
25447
- const text = String(value ?? '');
25448
- return mask.rawMode === 'alphanumeric'
25449
- ? text.replace(/[^a-zA-Z0-9]/g, '')
25450
- : digitsOnly(text);
25451
- }
25452
- function resolveBrazilianPhoneMask(value) {
25453
- const digits = digitsOnly(value);
25454
- if (digits.startsWith('55') && digits.length > 11) {
25455
- return '+00 00 00000-0000';
25456
- }
25457
- return digits.length <= 10 ? '(00) 0000-0000' : '(00) 00000-0000';
25458
- }
25459
- function hasPhoneSignal(metadata, signals) {
25460
- const phoneFormat = normalizeComparableToken(metadata['phoneFormat']);
25461
- const defaultCountry = normalizeComparableToken(metadata['defaultCountry']);
25462
- return (phoneFormat === 'national' ||
25463
- phoneFormat === 'international' ||
25464
- phoneFormat === 'e164' ||
25465
- defaultCountry === 'br' ||
25466
- hasSignal(signals, 'phone') ||
25467
- hasSignal(signals, 'telefone') ||
25468
- hasSignal(signals, 'celular') ||
25469
- hasSignal(signals, 'mobile') ||
25470
- hasSignal(signals, 'whatsapp') ||
25471
- hasSignal(signals, 'tel'));
25472
- }
25473
- function hasPostalCodeSignal(signals) {
25474
- return (hasSignal(signals, 'cep') ||
25475
- hasSignal(signals, 'postalcode') ||
25476
- hasSignal(signals, 'zipcode') ||
25477
- hasSignal(signals, 'zip'));
25478
- }
25479
- function buildComparableSignals(metadata) {
25480
- return [
25481
- metadata['name'],
25482
- metadata['id'],
25483
- metadata['key'],
25484
- metadata['label'],
25485
- metadata['placeholder'],
25486
- metadata['controlType'],
25487
- metadata['type'],
25488
- metadata['dataType'],
25489
- metadata['format'],
25490
- metadata['mask'],
25491
- metadata['inputMask'],
25492
- ]
25493
- .map(normalizeComparableToken)
25494
- .filter(Boolean)
25495
- .join('|');
25496
- }
25497
- function hasSignal(signals, token) {
25498
- return signals.split('|').includes(token) || signals.includes(token);
25499
- }
25500
- function maskFromFormat(value) {
25501
- const format = stringValue(value);
25502
- if (!format) {
25503
- return null;
25504
- }
25505
- const hasMaskTokens = /[0#9Xx]/.test(format);
25506
- const hasInvalidLetters = /[A-WYZawyz]/.test(format);
25507
- return hasMaskTokens && !hasInvalidLetters ? format : null;
25508
- }
25509
- function stringValue(value) {
25510
- return typeof value === 'string' && value.trim() ? value.trim() : null;
25511
- }
25512
- function normalizeComparableToken(value) {
25513
- return String(value ?? '')
25514
- .trim()
25515
- .toLowerCase()
25516
- .normalize('NFD')
25517
- .replace(/[\u0300-\u036f]/g, '')
25518
- .replace(/[\s_-]+/g, '');
25519
- }
25520
- function digitsOnly(value) {
25521
- return String(value ?? '').replace(/\D/g, '');
25522
- }
25523
- function isMaskPlaceholder(token) {
25524
- return token === '0' || token === '9' || token === '#' || token === 'X' || token === 'x';
25525
- }
25526
-
25527
25608
  class InlineInputComponent extends SimpleBaseInputComponent {
25528
25609
  readonlyMode = false;
25529
25610
  disabledMode = false;
package/index.d.ts CHANGED
@@ -705,6 +705,9 @@ declare abstract class SimpleBaseInputComponent implements ControlValueAccessor,
705
705
  protected getSpecificCssClasses(): string[];
706
706
  private setupFormControlIntegration;
707
707
  private syncComponentStateFromControl;
708
+ private handleMaskedNativeInput;
709
+ protected applyNativeDisplayMask(value?: unknown): void;
710
+ private resolveNativeDisplayMask;
708
711
  private refreshErrorTooltip;
709
712
  protected ariaInvalidAttribute(): 'true' | null;
710
713
  protected tDynamicFields(keySuffix: string, fallback: string, params?: Record<string, string | number | boolean | null | undefined>): string;
@@ -1108,6 +1111,7 @@ declare abstract class SimpleBaseSelectComponent<T = any> extends SimpleBaseInpu
1108
1111
  isSelected(value: T): boolean;
1109
1112
  isOptionDisabled(option: SelectOption<T>): boolean;
1110
1113
  protected areOptionValuesEqual(left: unknown, right: unknown): boolean;
1114
+ protected normalizeComparableOptionValue(value: unknown): unknown;
1111
1115
  /**
1112
1116
  * Toggles selection of all options when `selectAll` is enabled in multiple
1113
1117
  * selection mode.
@@ -2143,6 +2147,7 @@ declare class MaterialCurrencyComponent extends SimpleBaseInputComponent {
2143
2147
  /** Formats the input value without touching or changing focus state. */
2144
2148
  formatDisplayValue(): void;
2145
2149
  writeValue(value: unknown): void;
2150
+ protected applyNativeDisplayMask(): void;
2146
2151
  validateField(): Promise<ValidationErrors | null>;
2147
2152
  readonlyMode: boolean;
2148
2153
  set disabledMode(value: boolean);
@@ -5651,6 +5656,7 @@ declare class PhoneInputComponent extends SimpleBaseInputComponent {
5651
5656
  ngOnInit(): void;
5652
5657
  writeValue(value: unknown): void;
5653
5658
  protected registerInputElement(el: HTMLElement): void;
5659
+ protected applyNativeDisplayMask(): void;
5654
5660
  handleInput(event: Event): void;
5655
5661
  handleBlur(): void;
5656
5662
  validateField(): Promise<ValidationErrors | null>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisui/dynamic-fields",
3
- "version": "8.0.0-beta.24",
3
+ "version": "8.0.0-beta.25",
4
4
  "description": "Angular Material-based dynamic form fields for Praxis UI with lazy loading and metadata-driven rendering.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^20.1.0",
@@ -11,8 +11,8 @@
11
11
  "@angular/platform-browser": "^20.1.0",
12
12
  "@angular/router": "^20.1.0",
13
13
  "rxjs": "^7.8.0",
14
- "@praxisui/core": "^8.0.0-beta.24",
15
- "@praxisui/cron-builder": "^8.0.0-beta.24"
14
+ "@praxisui/core": "^8.0.0-beta.25",
15
+ "@praxisui/cron-builder": "^8.0.0-beta.25"
16
16
  },
17
17
  "dependencies": {
18
18
  "libphonenumber-js": "^1.12.41",