@praxisui/dynamic-fields 7.0.0-beta.0 → 8.0.0-beta.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.
@@ -39,6 +39,7 @@ import * as i2$1 from '@angular/material/slide-toggle';
39
39
  import { MatSlideToggleModule } from '@angular/material/slide-toggle';
40
40
  import * as i4$2 from '@angular/cdk/text-field';
41
41
  import { TextFieldModule } from '@angular/cdk/text-field';
42
+ import { parsePhoneNumberFromString, AsYouType } from 'libphonenumber-js';
42
43
  import { PraxisDialog } from '@praxisui/dialog';
43
44
  import { HttpClient, HttpHeaders, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
44
45
  import * as i7 from '@angular/material/menu';
@@ -281,6 +282,8 @@ const PRAXIS_DYNAMIC_FIELDS_EN_US = {
281
282
  'praxis.dynamicFields.checkboxGroup.optionUnavailableDescription': 'Option unavailable.',
282
283
  'praxis.dynamicFields.editorial.textInput.friendlyName': 'Text input',
283
284
  'praxis.dynamicFields.editorial.textInput.description': 'Simple text field with validation and icon/placeholder support.',
285
+ 'praxis.dynamicFields.editorial.phoneInput.friendlyName': 'Phone input',
286
+ 'praxis.dynamicFields.editorial.phoneInput.description': 'Country-aware phone field with progressive masking and semantic validation.',
284
287
  'praxis.dynamicFields.editorial.numericTextBox.friendlyName': 'Numeric input',
285
288
  'praxis.dynamicFields.editorial.numericTextBox.description': 'Numeric field with validation (min, max, pattern).',
286
289
  'praxis.dynamicFields.editorial.select.friendlyName': 'Select (Material)',
@@ -492,6 +495,8 @@ const PRAXIS_DYNAMIC_FIELDS_PT_BR = {
492
495
  'praxis.dynamicFields.checkboxGroup.optionUnavailableDescription': 'Opção indisponível.',
493
496
  'praxis.dynamicFields.editorial.textInput.friendlyName': 'Entrada de texto',
494
497
  'praxis.dynamicFields.editorial.textInput.description': 'Campo de texto simples com validação e suporte a ícones/placeholder.',
498
+ 'praxis.dynamicFields.editorial.phoneInput.friendlyName': 'Entrada de telefone',
499
+ 'praxis.dynamicFields.editorial.phoneInput.description': 'Campo telefônico com máscara progressiva por país e validação semântica.',
495
500
  'praxis.dynamicFields.editorial.numericTextBox.friendlyName': 'Entrada numérica',
496
501
  'praxis.dynamicFields.editorial.numericTextBox.description': 'Campo numérico com validação (mín, máx, padrão).',
497
502
  'praxis.dynamicFields.editorial.select.friendlyName': 'Seleção (Material)',
@@ -4434,7 +4439,7 @@ class MaterialCheckboxGroupComponent extends SimpleBaseSelectComponent {
4434
4439
  : !!mappedOptions?.length;
4435
4440
  super.setSelectMetadata({
4436
4441
  ...matMetadata,
4437
- options: mappedOptions,
4442
+ options: mappedOptions ?? [],
4438
4443
  multiple,
4439
4444
  searchable: matMetadata.searchable,
4440
4445
  selectAll: matMetadata.selectAll,
@@ -7604,168 +7609,234 @@ function createNumberInputComponentMetadata(locale = 'en-US') {
7604
7609
  return createWave1ComponentDocMeta(PDX_NUMBER_INPUT_EDITORIAL_DESCRIPTOR, locale);
7605
7610
  }
7606
7611
 
7607
- // =============================================================================
7608
- // INTERFACES ESPECÍFICAS DO TEXT-INPUT (herda BaseValidationConfig)
7609
- // =============================================================================
7610
- class TextInputComponent extends SimpleBaseInputComponent {
7611
- // =============================================================================
7612
- // OUTPUTS ESPECÍFICOS
7613
- // =============================================================================
7612
+ /**
7613
+ * Specialized input component for telephone numbers.
7614
+ *
7615
+ * Renders a `<mat-form-field>` wrapping an `<input type="tel">` element with
7616
+ * Material styling. Built on top of `SimpleBaseInputComponent` to leverage
7617
+ * reactive forms integration, hint/error messaging and validation hooks.
7618
+ */
7619
+ class PhoneInputComponent extends SimpleBaseInputComponent {
7620
+ /** Emits whenever validation state changes. */
7614
7621
  validationChange = output();
7615
7622
  // Praxis Field States
7616
7623
  readonlyMode = false;
7617
7624
  disabledMode = false;
7618
7625
  visible = true;
7619
7626
  presentationMode = false;
7620
- isReadonlyEffective() {
7621
- const st = computeEffectiveState(this.metadataAsField(), {
7622
- readonlyMode: this.readonlyMode,
7623
- presentationMode: this.presentationMode,
7624
- });
7625
- return st.readonly;
7626
- }
7627
- // =============================================================================
7628
- // COMPUTED PROPERTIES (validação e classes herdadas da base)
7629
- // =============================================================================
7630
- // Todas as computed properties (CSS classes, validação, Material Design) estão na base class
7631
- // =============================================================================
7632
- // LIFECYCLE
7633
- // =============================================================================
7627
+ allowedCharactersValidator = Validators.pattern(/^[0-9()+\-\s]*$/);
7628
+ semanticPhoneValidator = (control) => this.validatePhoneControl(control);
7634
7629
  ngOnInit() {
7635
- super.ngOnInit(); // Chama a inicialização da classe base (já inclui setupValidators)
7636
- try {
7637
- this.log('debug', 'TextInput initialized');
7630
+ super.ngOnInit();
7631
+ this.syncPhoneValidators();
7632
+ queueMicrotask(() => this.applyPhoneDisplayValue());
7633
+ }
7634
+ writeValue(value) {
7635
+ super.writeValue(value);
7636
+ queueMicrotask(() => this.applyPhoneDisplayValue());
7637
+ }
7638
+ registerInputElement(el) {
7639
+ super.registerInputElement(el);
7640
+ this.applyPhoneDisplayValue();
7641
+ }
7642
+ handleInput(event) {
7643
+ if (!this.isPhoneRuntimeEnabled()) {
7644
+ super.handleInput(event);
7645
+ return;
7638
7646
  }
7639
- catch (error) {
7640
- this.log('error', 'TextInput initialization failed', { error });
7647
+ const input = event.target;
7648
+ const rawValue = input?.value ?? '';
7649
+ const displayValue = this.formatPhoneDisplayValue(rawValue, {
7650
+ allowPartial: true,
7651
+ });
7652
+ const modelValue = this.resolveModelValue(displayValue);
7653
+ if (input && input.value !== displayValue) {
7654
+ input.value = displayValue;
7641
7655
  }
7642
- }
7643
- onComponentInit() {
7644
- // Inicialização específica do text-input
7645
- const meta = this.metadata();
7646
- if (meta) {
7647
- // Inicializar valor padrão se definido
7648
- if (meta.defaultValue !== undefined && this.control().value == null) {
7649
- this.control().setValue(meta.defaultValue, { emitEvent: false });
7650
- }
7656
+ this.setValue(modelValue, { emitEvent: true });
7657
+ if (input && input.value !== displayValue) {
7658
+ input.value = displayValue;
7651
7659
  }
7652
7660
  }
7653
- /**
7654
- * Adiciona classes CSS específicas do text-input
7655
- */
7656
- getSpecificCssClasses() {
7657
- return ['pdx-text-input'];
7658
- }
7659
- // =============================================================================
7660
- // EVENT HANDLERS (inherited from base, pode ser customizado se necessário)
7661
- // =============================================================================
7662
- // =============================================================================
7663
- // MÉTODOS PÚBLICOS ESPECÍFICOS
7664
- // =============================================================================
7665
- /**
7666
- * Reset do campo
7667
- */
7668
- resetField() {
7669
- const meta = this.metadata();
7670
- const defaultValue = meta?.defaultValue ?? null;
7671
- this.setValue(defaultValue, { emitEvent: false });
7672
- // Reset estados via base class
7673
- this.componentState.update((state) => ({
7674
- ...state,
7675
- touched: false,
7676
- dirty: false,
7677
- }));
7678
- this.fieldState.update((state) => ({
7679
- ...state,
7680
- value: defaultValue,
7681
- valid: true,
7682
- errors: null,
7683
- }));
7684
- const control = this.control();
7685
- control.markAsPristine();
7686
- control.markAsUntouched();
7661
+ handleBlur() {
7662
+ super.handleBlur();
7663
+ if (!this.isPhoneRuntimeEnabled()) {
7664
+ return;
7665
+ }
7666
+ const displayValue = this.formatPhoneDisplayValue(this.control().value, {
7667
+ allowPartial: false,
7668
+ });
7669
+ const modelValue = this.resolveModelValue(displayValue);
7670
+ if (modelValue !== this.control().value) {
7671
+ this.setValue(modelValue, { emitEvent: true });
7672
+ }
7673
+ else {
7674
+ this.control().updateValueAndValidity();
7675
+ }
7676
+ this.setNativeDisplayValue(displayValue);
7687
7677
  }
7688
- /**
7689
- * Força validação do campo (override para emitir evento)
7690
- */
7691
7678
  async validateField() {
7692
7679
  const errors = await super.validateField();
7693
7680
  this.validationChange.emit(errors);
7694
7681
  return errors;
7695
7682
  }
7696
- /**
7697
- * Define metadata e aplica configurações
7698
- */
7699
- setInputMetadata(metadata) {
7700
- this.setMetadata(metadata); // Base class já reaplica validators
7701
- // Reaplicar defaultValue dinamicamente sem recriar componente
7702
- const ctrl = this.control();
7703
- if (ctrl && ctrl.pristine && (ctrl.value == null || ctrl.value === '') &&
7704
- metadata.defaultValue !== undefined) {
7705
- ctrl.setValue(metadata.defaultValue, { emitEvent: false });
7706
- }
7707
- }
7708
- showClear() {
7709
- const cfg = this.clearButtonConfig();
7710
- if (cfg === undefined || cfg === null)
7711
- return false;
7712
- const enabled = typeof cfg === 'boolean' ? cfg : cfg.enabled === true;
7713
- if (!enabled)
7714
- return false;
7715
- if (this.disabledMode || this.isReadonlyEffective() || this.presentationMode)
7716
- return false;
7717
- if (cfg && typeof cfg === 'object' && cfg.showOnlyWhenFilled) {
7718
- const v = this.control().value;
7719
- return v !== null && v !== undefined && String(v) !== '';
7720
- }
7721
- return true;
7683
+ getSpecificCssClasses() {
7684
+ return ['pdx-phone-input'];
7722
7685
  }
7723
- onClearClick() {
7724
- // Prefer empty string for text input
7725
- this.setValue('', { emitEvent: true });
7726
- const control = this.control();
7727
- control.markAsDirty();
7728
- control.markAsTouched();
7686
+ /** Applies strongly typed metadata to the component. */
7687
+ setInputMetadata(metadata) {
7688
+ this.setMetadata(metadata);
7689
+ this.syncPhoneValidators();
7690
+ this.applyPhoneDisplayValue();
7729
7691
  }
7730
- // Material error state matcher derived from metadata configuration
7731
7692
  errorStateMatcher() {
7732
7693
  return getErrorStateMatcherForField(this.metadata());
7733
7694
  }
7734
- clearButtonConfig() {
7735
- const raw = this.metadataRecord()?.['clearButton'];
7736
- if (typeof raw === 'boolean') {
7737
- return raw;
7738
- }
7739
- if (raw && typeof raw === 'object') {
7740
- return raw;
7695
+ isReadonlyEffective() {
7696
+ const st = computeEffectiveState(this.metadataAsField(), {
7697
+ readonlyMode: this.readonlyMode,
7698
+ presentationMode: this.presentationMode,
7699
+ });
7700
+ return st.readonly;
7701
+ }
7702
+ metadataAsField() {
7703
+ const metadata = this.metadata();
7704
+ if (!metadata || typeof metadata !== 'object') {
7705
+ return null;
7741
7706
  }
7742
- return null;
7707
+ return metadata;
7743
7708
  }
7744
- metadataRecord() {
7709
+ phoneMetadata() {
7745
7710
  const metadata = this.metadata();
7746
7711
  return metadata && typeof metadata === 'object'
7747
7712
  ? metadata
7748
- : null;
7713
+ : {};
7749
7714
  }
7750
- metadataAsField() {
7751
- const metadata = this.metadata();
7752
- if (!metadata || typeof metadata !== 'object') {
7715
+ syncPhoneValidators() {
7716
+ const control = this.control();
7717
+ control.removeValidators([
7718
+ this.allowedCharactersValidator,
7719
+ this.semanticPhoneValidator,
7720
+ ]);
7721
+ control.addValidators(this.allowedCharactersValidator);
7722
+ if (this.phoneMetadata().validatePhoneNumber === true) {
7723
+ control.addValidators(this.semanticPhoneValidator);
7724
+ }
7725
+ control.updateValueAndValidity({ emitEvent: false });
7726
+ }
7727
+ validatePhoneControl(control) {
7728
+ const value = String(control.value ?? '').trim();
7729
+ if (!value) {
7753
7730
  return null;
7754
7731
  }
7755
- return metadata;
7732
+ const metadata = this.phoneMetadata();
7733
+ const phone = parsePhoneNumberFromString(value, this.resolveDefaultCountry());
7734
+ if (!phone?.isValid()) {
7735
+ return {
7736
+ phoneNumber: {
7737
+ message: 'Telefone invalido',
7738
+ defaultCountry: metadata.defaultCountry ?? null,
7739
+ },
7740
+ };
7741
+ }
7742
+ const allowedCountries = this.resolveAllowedCountries();
7743
+ if (allowedCountries.length) {
7744
+ const actualCountry = phone.country;
7745
+ if (!actualCountry || !allowedCountries.includes(actualCountry)) {
7746
+ return {
7747
+ phoneCountry: {
7748
+ message: 'Pais do telefone nao permitido',
7749
+ allowedCountries,
7750
+ actualCountry: actualCountry ?? null,
7751
+ },
7752
+ };
7753
+ }
7754
+ }
7755
+ return null;
7756
7756
  }
7757
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TextInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
7758
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: TextInputComponent, isStandalone: true, selector: "pdx-text-input", inputs: { readonlyMode: "readonlyMode", disabledMode: "disabledMode", visible: "visible", presentationMode: "presentationMode" }, outputs: { validationChange: "validationChange" }, host: { properties: { "class": "componentCssClasses()", "class.praxis-disabled": "disabledMode", "style.display": "visible ? \"block\" : \"none\"", "attr.aria-hidden": "visible ? null : \"true\"", "style.width": "\"100%\"", "attr.data-field-type": "\"input\"", "attr.data-field-name": "metadata()?.name", "attr.data-component-id": "componentId()" } }, providers: [
7757
+ isPhoneRuntimeEnabled() {
7758
+ const metadata = this.phoneMetadata();
7759
+ return (metadata.autoFormat === true ||
7760
+ metadata.validatePhoneNumber === true ||
7761
+ !!metadata.phoneFormat);
7762
+ }
7763
+ resolveDefaultCountry() {
7764
+ const country = String(this.phoneMetadata().defaultCountry ?? '')
7765
+ .trim()
7766
+ .toUpperCase();
7767
+ return /^[A-Z]{2}$/.test(country) ? country : undefined;
7768
+ }
7769
+ resolveAllowedCountries() {
7770
+ return (this.phoneMetadata().allowedCountries ?? [])
7771
+ .map((country) => String(country ?? '').trim().toUpperCase())
7772
+ .filter((country) => /^[A-Z]{2}$/.test(country));
7773
+ }
7774
+ resolvePhoneFormat() {
7775
+ return this.phoneMetadata().phoneFormat ?? 'national';
7776
+ }
7777
+ resolveModelValue(displayValue) {
7778
+ const value = String(displayValue ?? '').trim();
7779
+ if (!value) {
7780
+ return '';
7781
+ }
7782
+ const metadata = this.phoneMetadata();
7783
+ const phone = parsePhoneNumberFromString(value, this.resolveDefaultCountry());
7784
+ if (phone?.isValid() &&
7785
+ (metadata.validatePhoneNumber === true || metadata.phoneFormat === 'e164')) {
7786
+ return phone.number;
7787
+ }
7788
+ return displayValue;
7789
+ }
7790
+ formatPhoneDisplayValue(value, options) {
7791
+ const rawValue = String(value ?? '').trim();
7792
+ if (!rawValue) {
7793
+ return '';
7794
+ }
7795
+ const country = this.resolveDefaultCountry();
7796
+ const phone = parsePhoneNumberFromString(rawValue, country);
7797
+ const shouldFormatValidNumber = this.phoneMetadata().autoFormat === true || !!this.phoneMetadata().phoneFormat;
7798
+ if (phone?.isValid() && shouldFormatValidNumber) {
7799
+ switch (this.resolvePhoneFormat()) {
7800
+ case 'e164':
7801
+ return phone.number;
7802
+ case 'international':
7803
+ return phone.formatInternational();
7804
+ case 'national':
7805
+ default:
7806
+ return phone.formatNational();
7807
+ }
7808
+ }
7809
+ if (options.allowPartial && this.phoneMetadata().autoFormat === true) {
7810
+ return new AsYouType(country).input(rawValue);
7811
+ }
7812
+ return rawValue;
7813
+ }
7814
+ applyPhoneDisplayValue() {
7815
+ if (!this.isPhoneRuntimeEnabled()) {
7816
+ return;
7817
+ }
7818
+ const displayValue = this.formatPhoneDisplayValue(this.control().value, {
7819
+ allowPartial: false,
7820
+ });
7821
+ this.setNativeDisplayValue(displayValue);
7822
+ }
7823
+ setNativeDisplayValue(displayValue) {
7824
+ const input = this.nativeElement;
7825
+ if (input && input.value !== displayValue) {
7826
+ input.value = displayValue;
7827
+ }
7828
+ }
7829
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PhoneInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
7830
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PhoneInputComponent, isStandalone: true, selector: "pdx-phone-input", inputs: { readonlyMode: "readonlyMode", disabledMode: "disabledMode", visible: "visible", presentationMode: "presentationMode" }, outputs: { validationChange: "validationChange" }, host: { properties: { "class": "componentCssClasses()", "class.praxis-disabled": "disabledMode", "style.display": "visible ? null : \"none\"", "attr.aria-hidden": "visible ? null : \"true\"", "attr.data-field-type": "\"phone\"", "attr.data-field-name": "metadata()?.name", "attr.data-component-id": "componentId()" } }, providers: [
7759
7831
  {
7760
7832
  provide: NG_VALUE_ACCESSOR,
7761
- useExisting: forwardRef(() => TextInputComponent),
7833
+ useExisting: forwardRef(() => PhoneInputComponent),
7762
7834
  multi: true,
7763
7835
  },
7764
7836
  ], usesInheritance: true, ngImport: i0, template: `
7765
7837
  <mat-form-field
7766
7838
  [appearance]="materialAppearance()"
7767
7839
  [color]="materialColor()"
7768
- floatLabel="auto"
7769
7840
  [class]="componentCssClasses()"
7770
7841
  [floatLabel]="floatLabelBehavior()"
7771
7842
  [subscriptSizing]="metadata()?.materialDesign?.subscriptSizing || 'fixed'"
@@ -7785,16 +7856,387 @@ class TextInputComponent extends SimpleBaseInputComponent {
7785
7856
  shouldShowPlaceholder && placeholder ? placeholder : null
7786
7857
  "
7787
7858
  [required]="metadata()?.required || false"
7788
- [type]="inputType()"
7789
- [autocomplete]="metadata()?.autocomplete || 'off'"
7790
- [spellcheck]="metadata()?.spellcheck ?? true"
7791
7859
  [readonly]="isReadonlyEffective()"
7792
- [attr.aria-disabled]="disabledMode ? 'true' : null"
7793
- [maxlength]="metadata()?.maxLength || null"
7794
- [minlength]="metadata()?.minLength || null"
7860
+ [autocomplete]="metadata()?.autocomplete || 'tel'"
7861
+ [type]="inputType()"
7795
7862
  [attr.aria-label]="!label && placeholder ? placeholder : null"
7863
+ [attr.aria-disabled]="disabledMode ? 'true' : null"
7796
7864
  [attr.aria-required]="metadata()?.required ? 'true' : 'false'"
7797
- [attr.aria-invalid]="ariaInvalidAttribute()"
7865
+ [matTooltip]="tooltipEnabled() ? errorMessage() : null"
7866
+ [matTooltipDisabled]="!(tooltipEnabled() && !!errorMessage())"
7867
+ [matTooltipPosition]="tooltipPosition()"
7868
+ />
7869
+
7870
+ @if (metadata()?.suffixIcon) {
7871
+ <mat-icon
7872
+ matSuffix
7873
+ [color]="iconPalette(metadata()?.suffixIconColor)"
7874
+ [style.color]="iconCustomColor(metadata()?.suffixIconColor)"
7875
+ [praxisIcon]="metadata()!.suffixIcon"
7876
+ [matTooltip]="metadata()?.suffixIconTooltip || null"
7877
+ [attr.aria-label]="metadata()?.suffixIconAriaLabel || null"
7878
+ ></mat-icon>
7879
+ }
7880
+
7881
+
7882
+ @if (showClear()) {
7883
+ <button
7884
+ mat-icon-button
7885
+ matSuffix
7886
+ type="button"
7887
+ (click)="onClearClick()"
7888
+ [disabled]="disabledMode || isReadonlyEffective() || presentationMode"
7889
+ [matTooltip]="clearActionTooltip()"
7890
+ [attr.aria-label]="clearActionAriaLabel()"
7891
+ >
7892
+ <mat-icon
7893
+ [color]="iconPalette(metadata()?.clearButton?.iconColor)"
7894
+ [style.color]="iconCustomColor(metadata()?.clearButton?.iconColor)"
7895
+ [praxisIcon]="metadata()?.clearButton?.icon || 'mi:clear'"
7896
+ ></mat-icon>
7897
+ </button>
7898
+ }
7899
+
7900
+ @if (hasValidationError()) {
7901
+ <mat-error>{{ errorMessage() }}</mat-error>
7902
+ }
7903
+
7904
+ @if (metadata()?.hint && !hasValidationError()) {
7905
+ <mat-hint [align]="metadata()?.hintAlign || 'start'">{{
7906
+ metadata()!.hint
7907
+ }}</mat-hint>
7908
+ }
7909
+ </mat-form-field>
7910
+ `, 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"] }] });
7911
+ }
7912
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PhoneInputComponent, decorators: [{
7913
+ type: Component,
7914
+ args: [{
7915
+ selector: 'pdx-phone-input',
7916
+ standalone: true,
7917
+ template: `
7918
+ <mat-form-field
7919
+ [appearance]="materialAppearance()"
7920
+ [color]="materialColor()"
7921
+ [class]="componentCssClasses()"
7922
+ [floatLabel]="floatLabelBehavior()"
7923
+ [subscriptSizing]="metadata()?.materialDesign?.subscriptSizing || 'fixed'"
7924
+ [hideRequiredMarker]="metadata()?.materialDesign?.hideRequiredMarker || false"
7925
+ >
7926
+ <mat-label>{{ label }}</mat-label>
7927
+
7928
+ @if (metadata()?.prefixIcon) {
7929
+ <mat-icon matPrefix [color]="iconPalette(metadata()?.prefixIconColor)" [style.color]="iconCustomColor(metadata()?.prefixIconColor)" [praxisIcon]="metadata()!.prefixIcon"></mat-icon>
7930
+ }
7931
+
7932
+ <input
7933
+ matInput
7934
+ [formControl]="control()"
7935
+ [errorStateMatcher]="errorStateMatcher()"
7936
+ [attr.placeholder]="
7937
+ shouldShowPlaceholder && placeholder ? placeholder : null
7938
+ "
7939
+ [required]="metadata()?.required || false"
7940
+ [readonly]="isReadonlyEffective()"
7941
+ [autocomplete]="metadata()?.autocomplete || 'tel'"
7942
+ [type]="inputType()"
7943
+ [attr.aria-label]="!label && placeholder ? placeholder : null"
7944
+ [attr.aria-disabled]="disabledMode ? 'true' : null"
7945
+ [attr.aria-required]="metadata()?.required ? 'true' : 'false'"
7946
+ [matTooltip]="tooltipEnabled() ? errorMessage() : null"
7947
+ [matTooltipDisabled]="!(tooltipEnabled() && !!errorMessage())"
7948
+ [matTooltipPosition]="tooltipPosition()"
7949
+ />
7950
+
7951
+ @if (metadata()?.suffixIcon) {
7952
+ <mat-icon
7953
+ matSuffix
7954
+ [color]="iconPalette(metadata()?.suffixIconColor)"
7955
+ [style.color]="iconCustomColor(metadata()?.suffixIconColor)"
7956
+ [praxisIcon]="metadata()!.suffixIcon"
7957
+ [matTooltip]="metadata()?.suffixIconTooltip || null"
7958
+ [attr.aria-label]="metadata()?.suffixIconAriaLabel || null"
7959
+ ></mat-icon>
7960
+ }
7961
+
7962
+
7963
+ @if (showClear()) {
7964
+ <button
7965
+ mat-icon-button
7966
+ matSuffix
7967
+ type="button"
7968
+ (click)="onClearClick()"
7969
+ [disabled]="disabledMode || isReadonlyEffective() || presentationMode"
7970
+ [matTooltip]="clearActionTooltip()"
7971
+ [attr.aria-label]="clearActionAriaLabel()"
7972
+ >
7973
+ <mat-icon
7974
+ [color]="iconPalette(metadata()?.clearButton?.iconColor)"
7975
+ [style.color]="iconCustomColor(metadata()?.clearButton?.iconColor)"
7976
+ [praxisIcon]="metadata()?.clearButton?.icon || 'mi:clear'"
7977
+ ></mat-icon>
7978
+ </button>
7979
+ }
7980
+
7981
+ @if (hasValidationError()) {
7982
+ <mat-error>{{ errorMessage() }}</mat-error>
7983
+ }
7984
+
7985
+ @if (metadata()?.hint && !hasValidationError()) {
7986
+ <mat-hint [align]="metadata()?.hintAlign || 'start'">{{
7987
+ metadata()!.hint
7988
+ }}</mat-hint>
7989
+ }
7990
+ </mat-form-field>
7991
+ `,
7992
+ imports: [
7993
+ MatButtonModule,
7994
+ CommonModule,
7995
+ MatFormFieldModule,
7996
+ MatInputModule,
7997
+ MatIconModule,
7998
+ MatTooltipModule,
7999
+ PraxisIconDirective,
8000
+ ReactiveFormsModule,
8001
+ ],
8002
+ providers: [
8003
+ {
8004
+ provide: NG_VALUE_ACCESSOR,
8005
+ useExisting: forwardRef(() => PhoneInputComponent),
8006
+ multi: true,
8007
+ },
8008
+ ],
8009
+ host: {
8010
+ '[class]': 'componentCssClasses()',
8011
+ '[class.praxis-disabled]': 'disabledMode',
8012
+ '[style.display]': 'visible ? null : "none"',
8013
+ '[attr.aria-hidden]': 'visible ? null : "true"',
8014
+ '[attr.data-field-type]': '"phone"',
8015
+ '[attr.data-field-name]': 'metadata()?.name',
8016
+ '[attr.data-component-id]': 'componentId()',
8017
+ },
8018
+ }]
8019
+ }], propDecorators: { validationChange: [{ type: i0.Output, args: ["validationChange"] }], readonlyMode: [{
8020
+ type: Input
8021
+ }], disabledMode: [{
8022
+ type: Input
8023
+ }], visible: [{
8024
+ type: Input
8025
+ }], presentationMode: [{
8026
+ type: Input
8027
+ }] } });
8028
+
8029
+ const PDX_PHONE_INPUT_EDITORIAL_DESCRIPTOR = {
8030
+ controlType: FieldControlType.PHONE,
8031
+ componentMetaId: 'pdx-phone-input',
8032
+ selector: 'pdx-phone-input',
8033
+ component: PhoneInputComponent,
8034
+ friendlyName: wave1Message('praxis.dynamicFields.editorial.phoneInput.friendlyName'),
8035
+ description: wave1Message('praxis.dynamicFields.editorial.phoneInput.description'),
8036
+ tooltip: wave1Message('praxis.dynamicFields.editorial.phoneInput.description'),
8037
+ icon: 'call',
8038
+ family: 'text',
8039
+ track: 'material',
8040
+ tags: ['widget', 'field', 'input', 'phone', 'mask', 'validation', 'material'],
8041
+ inputs: createWave1StandardInputs('MaterialPhoneMetadata', 'praxis.dynamicFields.editorial.common.inputs.metadata.description'),
8042
+ outputs: createWave1StandardOutputs(),
8043
+ lib: '@praxisui/dynamic-fields',
8044
+ };
8045
+ function createPhoneInputComponentMetadata(locale = 'en-US') {
8046
+ return createWave1ComponentDocMeta(PDX_PHONE_INPUT_EDITORIAL_DESCRIPTOR, locale);
8047
+ }
8048
+
8049
+ // =============================================================================
8050
+ // INTERFACES ESPECÍFICAS DO TEXT-INPUT (herda BaseValidationConfig)
8051
+ // =============================================================================
8052
+ class TextInputComponent extends SimpleBaseInputComponent {
8053
+ // =============================================================================
8054
+ // OUTPUTS ESPECÍFICOS
8055
+ // =============================================================================
8056
+ validationChange = output();
8057
+ // Praxis Field States
8058
+ readonlyMode = false;
8059
+ disabledMode = false;
8060
+ visible = true;
8061
+ presentationMode = false;
8062
+ isReadonlyEffective() {
8063
+ const st = computeEffectiveState(this.metadataAsField(), {
8064
+ readonlyMode: this.readonlyMode,
8065
+ presentationMode: this.presentationMode,
8066
+ });
8067
+ return st.readonly;
8068
+ }
8069
+ // =============================================================================
8070
+ // COMPUTED PROPERTIES (validação e classes herdadas da base)
8071
+ // =============================================================================
8072
+ // Todas as computed properties (CSS classes, validação, Material Design) estão na base class
8073
+ // =============================================================================
8074
+ // LIFECYCLE
8075
+ // =============================================================================
8076
+ ngOnInit() {
8077
+ super.ngOnInit(); // Chama a inicialização da classe base (já inclui setupValidators)
8078
+ try {
8079
+ this.log('debug', 'TextInput initialized');
8080
+ }
8081
+ catch (error) {
8082
+ this.log('error', 'TextInput initialization failed', { error });
8083
+ }
8084
+ }
8085
+ onComponentInit() {
8086
+ // Inicialização específica do text-input
8087
+ const meta = this.metadata();
8088
+ if (meta) {
8089
+ // Inicializar valor padrão se definido
8090
+ if (meta.defaultValue !== undefined && this.control().value == null) {
8091
+ this.control().setValue(meta.defaultValue, { emitEvent: false });
8092
+ }
8093
+ }
8094
+ }
8095
+ /**
8096
+ * Adiciona classes CSS específicas do text-input
8097
+ */
8098
+ getSpecificCssClasses() {
8099
+ return ['pdx-text-input'];
8100
+ }
8101
+ // =============================================================================
8102
+ // EVENT HANDLERS (inherited from base, pode ser customizado se necessário)
8103
+ // =============================================================================
8104
+ // =============================================================================
8105
+ // MÉTODOS PÚBLICOS ESPECÍFICOS
8106
+ // =============================================================================
8107
+ /**
8108
+ * Reset do campo
8109
+ */
8110
+ resetField() {
8111
+ const meta = this.metadata();
8112
+ const defaultValue = meta?.defaultValue ?? null;
8113
+ this.setValue(defaultValue, { emitEvent: false });
8114
+ // Reset estados via base class
8115
+ this.componentState.update((state) => ({
8116
+ ...state,
8117
+ touched: false,
8118
+ dirty: false,
8119
+ }));
8120
+ this.fieldState.update((state) => ({
8121
+ ...state,
8122
+ value: defaultValue,
8123
+ valid: true,
8124
+ errors: null,
8125
+ }));
8126
+ const control = this.control();
8127
+ control.markAsPristine();
8128
+ control.markAsUntouched();
8129
+ }
8130
+ /**
8131
+ * Força validação do campo (override para emitir evento)
8132
+ */
8133
+ async validateField() {
8134
+ const errors = await super.validateField();
8135
+ this.validationChange.emit(errors);
8136
+ return errors;
8137
+ }
8138
+ /**
8139
+ * Define metadata e aplica configurações
8140
+ */
8141
+ setInputMetadata(metadata) {
8142
+ this.setMetadata(metadata); // Base class já reaplica validators
8143
+ // Reaplicar defaultValue dinamicamente sem recriar componente
8144
+ const ctrl = this.control();
8145
+ if (ctrl && ctrl.pristine && (ctrl.value == null || ctrl.value === '') &&
8146
+ metadata.defaultValue !== undefined) {
8147
+ ctrl.setValue(metadata.defaultValue, { emitEvent: false });
8148
+ }
8149
+ }
8150
+ showClear() {
8151
+ const cfg = this.clearButtonConfig();
8152
+ if (cfg === undefined || cfg === null)
8153
+ return false;
8154
+ const enabled = typeof cfg === 'boolean' ? cfg : cfg.enabled === true;
8155
+ if (!enabled)
8156
+ return false;
8157
+ if (this.disabledMode || this.isReadonlyEffective() || this.presentationMode)
8158
+ return false;
8159
+ if (cfg && typeof cfg === 'object' && cfg.showOnlyWhenFilled) {
8160
+ const v = this.control().value;
8161
+ return v !== null && v !== undefined && String(v) !== '';
8162
+ }
8163
+ return true;
8164
+ }
8165
+ onClearClick() {
8166
+ // Prefer empty string for text input
8167
+ this.setValue('', { emitEvent: true });
8168
+ const control = this.control();
8169
+ control.markAsDirty();
8170
+ control.markAsTouched();
8171
+ }
8172
+ // Material error state matcher derived from metadata configuration
8173
+ errorStateMatcher() {
8174
+ return getErrorStateMatcherForField(this.metadata());
8175
+ }
8176
+ clearButtonConfig() {
8177
+ const raw = this.metadataRecord()?.['clearButton'];
8178
+ if (typeof raw === 'boolean') {
8179
+ return raw;
8180
+ }
8181
+ if (raw && typeof raw === 'object') {
8182
+ return raw;
8183
+ }
8184
+ return null;
8185
+ }
8186
+ metadataRecord() {
8187
+ const metadata = this.metadata();
8188
+ return metadata && typeof metadata === 'object'
8189
+ ? metadata
8190
+ : null;
8191
+ }
8192
+ metadataAsField() {
8193
+ const metadata = this.metadata();
8194
+ if (!metadata || typeof metadata !== 'object') {
8195
+ return null;
8196
+ }
8197
+ return metadata;
8198
+ }
8199
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TextInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
8200
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: TextInputComponent, isStandalone: true, selector: "pdx-text-input", inputs: { readonlyMode: "readonlyMode", disabledMode: "disabledMode", visible: "visible", presentationMode: "presentationMode" }, outputs: { validationChange: "validationChange" }, host: { properties: { "class": "componentCssClasses()", "class.praxis-disabled": "disabledMode", "style.display": "visible ? \"block\" : \"none\"", "attr.aria-hidden": "visible ? null : \"true\"", "style.width": "\"100%\"", "attr.data-field-type": "\"input\"", "attr.data-field-name": "metadata()?.name", "attr.data-component-id": "componentId()" } }, providers: [
8201
+ {
8202
+ provide: NG_VALUE_ACCESSOR,
8203
+ useExisting: forwardRef(() => TextInputComponent),
8204
+ multi: true,
8205
+ },
8206
+ ], usesInheritance: true, ngImport: i0, template: `
8207
+ <mat-form-field
8208
+ [appearance]="materialAppearance()"
8209
+ [color]="materialColor()"
8210
+ floatLabel="auto"
8211
+ [class]="componentCssClasses()"
8212
+ [floatLabel]="floatLabelBehavior()"
8213
+ [subscriptSizing]="metadata()?.materialDesign?.subscriptSizing || 'fixed'"
8214
+ [hideRequiredMarker]="metadata()?.materialDesign?.hideRequiredMarker || false"
8215
+ >
8216
+ <mat-label>{{ label }}</mat-label>
8217
+
8218
+ @if (metadata()?.prefixIcon) {
8219
+ <mat-icon matPrefix [color]="iconPalette(metadata()?.prefixIconColor)" [style.color]="iconCustomColor(metadata()?.prefixIconColor)" [praxisIcon]="metadata()!.prefixIcon"></mat-icon>
8220
+ }
8221
+
8222
+ <input
8223
+ matInput
8224
+ [formControl]="control()"
8225
+ [errorStateMatcher]="errorStateMatcher()"
8226
+ [attr.placeholder]="
8227
+ shouldShowPlaceholder && placeholder ? placeholder : null
8228
+ "
8229
+ [required]="metadata()?.required || false"
8230
+ [type]="inputType()"
8231
+ [autocomplete]="metadata()?.autocomplete || 'off'"
8232
+ [spellcheck]="metadata()?.spellcheck ?? true"
8233
+ [readonly]="isReadonlyEffective()"
8234
+ [attr.aria-disabled]="disabledMode ? 'true' : null"
8235
+ [maxlength]="metadata()?.maxLength || null"
8236
+ [minlength]="metadata()?.minLength || null"
8237
+ [attr.aria-label]="!label && placeholder ? placeholder : null"
8238
+ [attr.aria-required]="metadata()?.required ? 'true' : 'false'"
8239
+ [attr.aria-invalid]="ariaInvalidAttribute()"
7798
8240
  [matTooltip]="tooltipEnabled() ? errorMessage() : null"
7799
8241
  [matTooltipDisabled]="!(tooltipEnabled() && !!errorMessage())"
7800
8242
  [matTooltipPosition]="tooltipPosition()"
@@ -7999,6 +8441,7 @@ function createTextInputComponentMetadata(locale = 'en-US') {
7999
8441
 
8000
8442
  const PRAXIS_DYNAMIC_FIELDS_WAVE_1_COMPONENT_METADATA = [
8001
8443
  createTextInputComponentMetadata(),
8444
+ createPhoneInputComponentMetadata(),
8002
8445
  createNumberInputComponentMetadata(),
8003
8446
  createMaterialSelectComponentMetadata(),
8004
8447
  createMaterialTextareaComponentMetadata(),
@@ -8012,6 +8455,7 @@ const PRAXIS_DYNAMIC_FIELDS_WAVE_1_COMPONENT_METADATA = [
8012
8455
  ];
8013
8456
  const PRAXIS_DYNAMIC_FIELDS_EDITORIAL_WAVE_1 = [
8014
8457
  PDX_TEXT_INPUT_EDITORIAL_DESCRIPTOR,
8458
+ PDX_PHONE_INPUT_EDITORIAL_DESCRIPTOR,
8015
8459
  PDX_NUMBER_INPUT_EDITORIAL_DESCRIPTOR,
8016
8460
  PDX_MATERIAL_SELECT_EDITORIAL_DESCRIPTOR,
8017
8461
  PDX_MATERIAL_TEXTAREA_EDITORIAL_DESCRIPTOR,
@@ -10747,19 +11191,10 @@ class DynamicFieldLoaderDirective {
10747
11191
  */
10748
11192
  applyGlobalStates() {
10749
11193
  this.shellRefs.forEach((shellRef) => {
10750
- if (this.readonlyMode !== null) {
10751
- shellRef.instance.readonlyMode = this.readonlyMode === true;
10752
- }
10753
- if (this.disabledMode !== null) {
10754
- shellRef.instance.disabledMode = this.disabledMode === true;
10755
- }
10756
- if (this.presentationMode !== null) {
10757
- shellRef.instance.presentationMode = this.presentationMode === true;
10758
- }
10759
- if (this.visible !== null) {
10760
- // visível por padrão; apenas aplicar quando definido
10761
- shellRef.instance.visible = this.visible !== false ? true : false;
10762
- }
11194
+ shellRef.instance.readonlyMode = this.readonlyMode === true;
11195
+ shellRef.instance.disabledMode = this.disabledMode === true;
11196
+ shellRef.instance.presentationMode = this.presentationMode === true;
11197
+ shellRef.instance.visible = this.visible !== false ? true : false;
10763
11198
  shellRef.changeDetectorRef.detectChanges();
10764
11199
  const compRef = this.componentRefs.get(shellRef.instance.field?.name);
10765
11200
  if (compRef) {
@@ -43862,254 +44297,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
43862
44297
  type: Input
43863
44298
  }] } });
43864
44299
 
43865
- /**
43866
- * Specialized input component for telephone numbers.
43867
- *
43868
- * Renders a `<mat-form-field>` wrapping an `<input type="tel">` element with
43869
- * Material styling. Built on top of `SimpleBaseInputComponent` to leverage
43870
- * reactive forms integration, hint/error messaging and validation hooks.
43871
- */
43872
- class PhoneInputComponent extends SimpleBaseInputComponent {
43873
- /** Emits whenever validation state changes. */
43874
- validationChange = output();
43875
- // Praxis Field States
43876
- readonlyMode = false;
43877
- disabledMode = false;
43878
- visible = true;
43879
- presentationMode = false;
43880
- ngOnInit() {
43881
- super.ngOnInit();
43882
- this.control().addValidators(Validators.pattern(/^[0-9()+\-\s]*$/));
43883
- }
43884
- async validateField() {
43885
- const errors = await super.validateField();
43886
- this.validationChange.emit(errors);
43887
- return errors;
43888
- }
43889
- getSpecificCssClasses() {
43890
- return ['pdx-phone-input'];
43891
- }
43892
- /** Applies strongly typed metadata to the component. */
43893
- setInputMetadata(metadata) {
43894
- this.setMetadata(metadata);
43895
- }
43896
- errorStateMatcher() {
43897
- return getErrorStateMatcherForField(this.metadata());
43898
- }
43899
- isReadonlyEffective() {
43900
- const st = computeEffectiveState(this.metadataAsField(), {
43901
- readonlyMode: this.readonlyMode,
43902
- presentationMode: this.presentationMode,
43903
- });
43904
- return st.readonly;
43905
- }
43906
- metadataAsField() {
43907
- const metadata = this.metadata();
43908
- if (!metadata || typeof metadata !== 'object') {
43909
- return null;
43910
- }
43911
- return metadata;
43912
- }
43913
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PhoneInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
43914
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PhoneInputComponent, isStandalone: true, selector: "pdx-phone-input", inputs: { readonlyMode: "readonlyMode", disabledMode: "disabledMode", visible: "visible", presentationMode: "presentationMode" }, outputs: { validationChange: "validationChange" }, host: { properties: { "class": "componentCssClasses()", "class.praxis-disabled": "disabledMode", "style.display": "visible ? null : \"none\"", "attr.aria-hidden": "visible ? null : \"true\"", "attr.data-field-type": "\"phone\"", "attr.data-field-name": "metadata()?.name", "attr.data-component-id": "componentId()" } }, providers: [
43915
- {
43916
- provide: NG_VALUE_ACCESSOR,
43917
- useExisting: forwardRef(() => PhoneInputComponent),
43918
- multi: true,
43919
- },
43920
- ], usesInheritance: true, ngImport: i0, template: `
43921
- <mat-form-field
43922
- [appearance]="materialAppearance()"
43923
- [color]="materialColor()"
43924
- [class]="componentCssClasses()"
43925
- [floatLabel]="floatLabelBehavior()"
43926
- [subscriptSizing]="metadata()?.materialDesign?.subscriptSizing || 'fixed'"
43927
- [hideRequiredMarker]="metadata()?.materialDesign?.hideRequiredMarker || false"
43928
- >
43929
- <mat-label>{{ label }}</mat-label>
43930
-
43931
- @if (metadata()?.prefixIcon) {
43932
- <mat-icon matPrefix [color]="iconPalette(metadata()?.prefixIconColor)" [style.color]="iconCustomColor(metadata()?.prefixIconColor)" [praxisIcon]="metadata()!.prefixIcon"></mat-icon>
43933
- }
43934
-
43935
- <input
43936
- matInput
43937
- [formControl]="control()"
43938
- [errorStateMatcher]="errorStateMatcher()"
43939
- [attr.placeholder]="
43940
- shouldShowPlaceholder && placeholder ? placeholder : null
43941
- "
43942
- [required]="metadata()?.required || false"
43943
- [readonly]="isReadonlyEffective()"
43944
- [autocomplete]="metadata()?.autocomplete || 'tel'"
43945
- [type]="inputType()"
43946
- [attr.aria-label]="!label && placeholder ? placeholder : null"
43947
- [attr.aria-disabled]="disabledMode ? 'true' : null"
43948
- [attr.aria-required]="metadata()?.required ? 'true' : 'false'"
43949
- [matTooltip]="tooltipEnabled() ? errorMessage() : null"
43950
- [matTooltipDisabled]="!(tooltipEnabled() && !!errorMessage())"
43951
- [matTooltipPosition]="tooltipPosition()"
43952
- />
43953
-
43954
- @if (metadata()?.suffixIcon) {
43955
- <mat-icon
43956
- matSuffix
43957
- [color]="iconPalette(metadata()?.suffixIconColor)"
43958
- [style.color]="iconCustomColor(metadata()?.suffixIconColor)"
43959
- [praxisIcon]="metadata()!.suffixIcon"
43960
- [matTooltip]="metadata()?.suffixIconTooltip || null"
43961
- [attr.aria-label]="metadata()?.suffixIconAriaLabel || null"
43962
- ></mat-icon>
43963
- }
43964
-
43965
-
43966
- @if (showClear()) {
43967
- <button
43968
- mat-icon-button
43969
- matSuffix
43970
- type="button"
43971
- (click)="onClearClick()"
43972
- [disabled]="disabledMode || isReadonlyEffective() || presentationMode"
43973
- [matTooltip]="clearActionTooltip()"
43974
- [attr.aria-label]="clearActionAriaLabel()"
43975
- >
43976
- <mat-icon
43977
- [color]="iconPalette(metadata()?.clearButton?.iconColor)"
43978
- [style.color]="iconCustomColor(metadata()?.clearButton?.iconColor)"
43979
- [praxisIcon]="metadata()?.clearButton?.icon || 'mi:clear'"
43980
- ></mat-icon>
43981
- </button>
43982
- }
43983
-
43984
- @if (hasValidationError()) {
43985
- <mat-error>{{ errorMessage() }}</mat-error>
43986
- }
43987
-
43988
- @if (metadata()?.hint && !hasValidationError()) {
43989
- <mat-hint [align]="metadata()?.hintAlign || 'start'">{{
43990
- metadata()!.hint
43991
- }}</mat-hint>
43992
- }
43993
- </mat-form-field>
43994
- `, 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"] }] });
43995
- }
43996
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PhoneInputComponent, decorators: [{
43997
- type: Component,
43998
- args: [{
43999
- selector: 'pdx-phone-input',
44000
- standalone: true,
44001
- template: `
44002
- <mat-form-field
44003
- [appearance]="materialAppearance()"
44004
- [color]="materialColor()"
44005
- [class]="componentCssClasses()"
44006
- [floatLabel]="floatLabelBehavior()"
44007
- [subscriptSizing]="metadata()?.materialDesign?.subscriptSizing || 'fixed'"
44008
- [hideRequiredMarker]="metadata()?.materialDesign?.hideRequiredMarker || false"
44009
- >
44010
- <mat-label>{{ label }}</mat-label>
44011
-
44012
- @if (metadata()?.prefixIcon) {
44013
- <mat-icon matPrefix [color]="iconPalette(metadata()?.prefixIconColor)" [style.color]="iconCustomColor(metadata()?.prefixIconColor)" [praxisIcon]="metadata()!.prefixIcon"></mat-icon>
44014
- }
44015
-
44016
- <input
44017
- matInput
44018
- [formControl]="control()"
44019
- [errorStateMatcher]="errorStateMatcher()"
44020
- [attr.placeholder]="
44021
- shouldShowPlaceholder && placeholder ? placeholder : null
44022
- "
44023
- [required]="metadata()?.required || false"
44024
- [readonly]="isReadonlyEffective()"
44025
- [autocomplete]="metadata()?.autocomplete || 'tel'"
44026
- [type]="inputType()"
44027
- [attr.aria-label]="!label && placeholder ? placeholder : null"
44028
- [attr.aria-disabled]="disabledMode ? 'true' : null"
44029
- [attr.aria-required]="metadata()?.required ? 'true' : 'false'"
44030
- [matTooltip]="tooltipEnabled() ? errorMessage() : null"
44031
- [matTooltipDisabled]="!(tooltipEnabled() && !!errorMessage())"
44032
- [matTooltipPosition]="tooltipPosition()"
44033
- />
44034
-
44035
- @if (metadata()?.suffixIcon) {
44036
- <mat-icon
44037
- matSuffix
44038
- [color]="iconPalette(metadata()?.suffixIconColor)"
44039
- [style.color]="iconCustomColor(metadata()?.suffixIconColor)"
44040
- [praxisIcon]="metadata()!.suffixIcon"
44041
- [matTooltip]="metadata()?.suffixIconTooltip || null"
44042
- [attr.aria-label]="metadata()?.suffixIconAriaLabel || null"
44043
- ></mat-icon>
44044
- }
44045
-
44046
-
44047
- @if (showClear()) {
44048
- <button
44049
- mat-icon-button
44050
- matSuffix
44051
- type="button"
44052
- (click)="onClearClick()"
44053
- [disabled]="disabledMode || isReadonlyEffective() || presentationMode"
44054
- [matTooltip]="clearActionTooltip()"
44055
- [attr.aria-label]="clearActionAriaLabel()"
44056
- >
44057
- <mat-icon
44058
- [color]="iconPalette(metadata()?.clearButton?.iconColor)"
44059
- [style.color]="iconCustomColor(metadata()?.clearButton?.iconColor)"
44060
- [praxisIcon]="metadata()?.clearButton?.icon || 'mi:clear'"
44061
- ></mat-icon>
44062
- </button>
44063
- }
44064
-
44065
- @if (hasValidationError()) {
44066
- <mat-error>{{ errorMessage() }}</mat-error>
44067
- }
44068
-
44069
- @if (metadata()?.hint && !hasValidationError()) {
44070
- <mat-hint [align]="metadata()?.hintAlign || 'start'">{{
44071
- metadata()!.hint
44072
- }}</mat-hint>
44073
- }
44074
- </mat-form-field>
44075
- `,
44076
- imports: [
44077
- MatButtonModule,
44078
- CommonModule,
44079
- MatFormFieldModule,
44080
- MatInputModule,
44081
- MatIconModule,
44082
- MatTooltipModule,
44083
- PraxisIconDirective,
44084
- ReactiveFormsModule,
44085
- ],
44086
- providers: [
44087
- {
44088
- provide: NG_VALUE_ACCESSOR,
44089
- useExisting: forwardRef(() => PhoneInputComponent),
44090
- multi: true,
44091
- },
44092
- ],
44093
- host: {
44094
- '[class]': 'componentCssClasses()',
44095
- '[class.praxis-disabled]': 'disabledMode',
44096
- '[style.display]': 'visible ? null : "none"',
44097
- '[attr.aria-hidden]': 'visible ? null : "true"',
44098
- '[attr.data-field-type]': '"phone"',
44099
- '[attr.data-field-name]': 'metadata()?.name',
44100
- '[attr.data-component-id]': 'componentId()',
44101
- },
44102
- }]
44103
- }], propDecorators: { validationChange: [{ type: i0.Output, args: ["validationChange"] }], readonlyMode: [{
44104
- type: Input
44105
- }], disabledMode: [{
44106
- type: Input
44107
- }], visible: [{
44108
- type: Input
44109
- }], presentationMode: [{
44110
- type: Input
44111
- }] } });
44112
-
44113
44300
  /**
44114
44301
  * Specialized input component for HTML time inputs.
44115
44302
  *
@@ -48854,26 +49041,7 @@ const PDX_PASSWORD_INPUT_COMPONENT_METADATA = {
48854
49041
  lib: '@praxisui/dynamic-fields',
48855
49042
  };
48856
49043
 
48857
- const PDX_PHONE_INPUT_COMPONENT_METADATA = {
48858
- id: 'pdx-phone-input',
48859
- selector: 'pdx-phone-input',
48860
- component: PhoneInputComponent,
48861
- friendlyName: 'Telefone (input)',
48862
- description: 'Campo de telefone com máscara e validação.',
48863
- icon: 'call',
48864
- inputs: [
48865
- { name: 'metadata', type: 'MaterialPhoneMetadata', description: 'Configuração do campo' },
48866
- { name: 'readonlyMode', type: 'boolean', default: false, description: 'Somente leitura' },
48867
- { name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilitado' },
48868
- { name: 'visible', type: 'boolean', default: true, description: 'Visibilidade' },
48869
- { name: 'presentationMode', type: 'boolean', default: false, description: 'Modo apresentação' },
48870
- ],
48871
- outputs: [
48872
- { name: 'validationChange', type: 'ValidationErrors | null', description: 'Emite ao validar' },
48873
- ],
48874
- tags: ['widget', 'field', 'input', 'material', 'phone'],
48875
- lib: '@praxisui/dynamic-fields',
48876
- };
49044
+ const PDX_PHONE_INPUT_COMPONENT_METADATA = createPhoneInputComponentMetadata();
48877
49045
 
48878
49046
  const PDX_PRELOAD_STATUS_COMPONENT_METADATA = {
48879
49047
  id: 'pdx-preload-status',
@@ -49770,7 +49938,7 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49770
49938
  track: 'primary-form',
49771
49939
  family: 'text',
49772
49940
  friendlyName: 'Phone input',
49773
- description: 'Specialized phone field.',
49941
+ description: 'Specialized phone field with country-aware masking and validation.',
49774
49942
  tags: ['text', 'phone', 'formatted'],
49775
49943
  valueShape: 'string',
49776
49944
  recommendedWhen: ['phone', 'contact'],
@@ -49778,6 +49946,51 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49778
49946
  dataSourceKind: 'local',
49779
49947
  status: 'partial-docs',
49780
49948
  apiPath: jsonApiPath('phone-input/pdx-phone-input.json-api.md'),
49949
+ metadata: {
49950
+ defaultCountry: 'BR',
49951
+ phoneFormat: 'national',
49952
+ autoFormat: true,
49953
+ validatePhoneNumber: true,
49954
+ },
49955
+ previewPresets: [
49956
+ {
49957
+ id: 'br-national',
49958
+ label: 'BR national',
49959
+ description: 'Formats Brazilian mobile numbers with DDD and stores the canonical E.164 value.',
49960
+ metadataPatch: {
49961
+ defaultCountry: 'BR',
49962
+ phoneFormat: 'national',
49963
+ autoFormat: true,
49964
+ validatePhoneNumber: true,
49965
+ },
49966
+ initialValue: '+5511987654321',
49967
+ },
49968
+ {
49969
+ id: 'br-e164',
49970
+ label: 'BR E.164',
49971
+ description: 'Keeps Brazilian numbers in the canonical E.164 presentation.',
49972
+ metadataPatch: {
49973
+ defaultCountry: 'BR',
49974
+ phoneFormat: 'e164',
49975
+ autoFormat: true,
49976
+ validatePhoneNumber: true,
49977
+ },
49978
+ initialValue: '+5511987654321',
49979
+ },
49980
+ {
49981
+ id: 'us-international',
49982
+ label: 'US international',
49983
+ description: 'Restricts semantic validation to US numbers and renders international formatting.',
49984
+ metadataPatch: {
49985
+ defaultCountry: 'US',
49986
+ allowedCountries: ['US'],
49987
+ phoneFormat: 'international',
49988
+ autoFormat: true,
49989
+ validatePhoneNumber: true,
49990
+ },
49991
+ initialValue: '+14155552671',
49992
+ },
49993
+ ],
49781
49994
  }),
49782
49995
  createEntry({
49783
49996
  id: 'url-input',
@@ -49808,6 +50021,38 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49808
50021
  dataSourceKind: 'local',
49809
50022
  status: 'partial-docs',
49810
50023
  apiPath: jsonApiPath('material-cpf-cnpj-input/pdx-material-cpf-cnpj-input.json-api.md'),
50024
+ previewPresets: [
50025
+ {
50026
+ id: 'cpf',
50027
+ label: 'CPF',
50028
+ description: 'Forces numeric CPF masking and check-digit validation.',
50029
+ metadataPatch: {
50030
+ documentType: 'cpf',
50031
+ version: 'legacy',
50032
+ },
50033
+ initialValue: '529.982.247-25',
50034
+ },
50035
+ {
50036
+ id: 'cnpj',
50037
+ label: 'CNPJ',
50038
+ description: 'Forces numeric CNPJ masking and legacy check-digit validation.',
50039
+ metadataPatch: {
50040
+ documentType: 'cnpj',
50041
+ version: 'legacy',
50042
+ },
50043
+ initialValue: '11.444.777/0001-61',
50044
+ },
50045
+ {
50046
+ id: 'cnpj-alpha',
50047
+ label: 'CNPJ alpha',
50048
+ description: 'Forces alphanumeric CNPJ masking and alphanumeric check-digit validation.',
50049
+ metadataPatch: {
50050
+ documentType: 'cnpj',
50051
+ version: 'alpha',
50052
+ },
50053
+ initialValue: '12.ABC.345/01DE-35',
50054
+ },
50055
+ ],
49811
50056
  }),
49812
50057
  createWave1Entry({
49813
50058
  id: 'number-input',
@@ -49840,6 +50085,57 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49840
50085
  stateRecipes: [
49841
50086
  createDefaultSeedRecipe(18450.75, 'Seeded default preview to expose formatted monetary rendering at first glance.'),
49842
50087
  ],
50088
+ previewPresets: [
50089
+ {
50090
+ id: 'brl',
50091
+ label: 'BRL',
50092
+ description: 'Uses Brazilian Portuguese grouping, decimal separators, and BRL symbol placement.',
50093
+ metadataPatch: {
50094
+ currency: 'BRL',
50095
+ locale: 'pt-BR',
50096
+ decimalPlaces: 2,
50097
+ currencyPosition: 'before',
50098
+ },
50099
+ initialValue: 18450.75,
50100
+ },
50101
+ {
50102
+ id: 'usd',
50103
+ label: 'USD',
50104
+ description: 'Uses US English grouping and decimal separators with two decimal places.',
50105
+ metadataPatch: {
50106
+ currency: 'USD',
50107
+ locale: 'en-US',
50108
+ decimalPlaces: 2,
50109
+ currencyPosition: 'before',
50110
+ },
50111
+ initialValue: 9876.5,
50112
+ },
50113
+ {
50114
+ id: 'jpy',
50115
+ label: 'JPY',
50116
+ description: 'Uses Japanese yen formatting with zero decimal places.',
50117
+ metadataPatch: {
50118
+ currency: 'JPY',
50119
+ locale: 'ja-JP',
50120
+ currencyPosition: 'after',
50121
+ },
50122
+ initialValue: 123456,
50123
+ },
50124
+ {
50125
+ id: 'brl-range',
50126
+ label: 'BRL range',
50127
+ description: 'Applies BRL masking with numeric min and max validation.',
50128
+ metadataPatch: {
50129
+ currency: 'BRL',
50130
+ locale: 'pt-BR',
50131
+ decimalPlaces: 2,
50132
+ currencyPosition: 'before',
50133
+ min: 0,
50134
+ max: 1000,
50135
+ },
50136
+ initialValue: 250.75,
50137
+ },
50138
+ ],
49843
50139
  }),
49844
50140
  createEntry({
49845
50141
  id: 'slider',
@@ -49879,7 +50175,7 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49879
50175
  friendlyName: 'Price range',
49880
50176
  description: 'Currency range with dedicated semantics.',
49881
50177
  tags: ['number', 'currency', 'range'],
49882
- valueShape: '{ min, max }',
50178
+ valueShape: '{ minPrice, maxPrice }',
49883
50179
  recommendedWhen: ['price range', 'salary band', 'minimum and maximum budget'],
49884
50180
  avoidWhen: ['single monetary value'],
49885
50181
  dataSourceKind: 'local',
@@ -49887,6 +50183,54 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49887
50183
  status: 'partial-docs',
49888
50184
  apiPath: jsonApiPath('material-price-range/pdx-material-price-range.json-api.md'),
49889
50185
  metadata: { currency: 'BRL', locale: 'pt-BR' },
50186
+ stateRecipes: [
50187
+ createDefaultSeedRecipe({ minPrice: 1200.5, maxPrice: 5800.75 }, 'Seeded default preview to expose the canonical monetary range payload immediately.'),
50188
+ createFilledSeedRecipe({ minPrice: 3500, maxPrice: 7500 }, 'Filled preview keeps the runtime value aligned with the price range contract.'),
50189
+ ],
50190
+ previewPresets: [
50191
+ {
50192
+ id: 'brl-band',
50193
+ label: 'BRL band',
50194
+ description: 'Uses BRL masking in both range endpoints with Brazilian separators.',
50195
+ metadataPatch: {
50196
+ currency: 'BRL',
50197
+ locale: 'pt-BR',
50198
+ decimalPlaces: 2,
50199
+ currencyPosition: 'before',
50200
+ startLabel: 'Minimum price',
50201
+ endLabel: 'Maximum price',
50202
+ },
50203
+ initialValue: { minPrice: 1200.5, maxPrice: 5800.75 },
50204
+ },
50205
+ {
50206
+ id: 'usd-band',
50207
+ label: 'USD band',
50208
+ description: 'Uses USD masking in both range endpoints with US separators.',
50209
+ metadataPatch: {
50210
+ currency: 'USD',
50211
+ locale: 'en-US',
50212
+ decimalPlaces: 2,
50213
+ currencyPosition: 'before',
50214
+ startLabel: 'Minimum price',
50215
+ endLabel: 'Maximum price',
50216
+ },
50217
+ initialValue: { minPrice: 2500, maxPrice: 12500.5 },
50218
+ },
50219
+ {
50220
+ id: 'brl-limited',
50221
+ label: 'BRL limited',
50222
+ description: 'Adds min and max validation while preserving BRL masking.',
50223
+ metadataPatch: {
50224
+ currency: 'BRL',
50225
+ locale: 'pt-BR',
50226
+ decimalPlaces: 2,
50227
+ currencyPosition: 'before',
50228
+ min: 0,
50229
+ max: 10000,
50230
+ },
50231
+ initialValue: { minPrice: 3500, maxPrice: 7500 },
50232
+ },
50233
+ ],
49890
50234
  }),
49891
50235
  createEntry({
49892
50236
  id: 'rating',
@@ -49945,6 +50289,7 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49945
50289
  apiPath: jsonApiPath('material-date-range/pdx-material-date-range.json-api.md'),
49946
50290
  stateRecipes: [
49947
50291
  createDefaultSeedRecipe({ startDate: '2026-03-01', endDate: '2026-03-23' }, 'Seeded default preview to demonstrate a complete temporal range without switching states.'),
50292
+ createFilledSeedRecipe({ startDate: '2026-04-01', endDate: '2026-04-30' }, 'Filled preview keeps the runtime value aligned with the published date-range payload.'),
49948
50293
  ],
49949
50294
  }),
49950
50295
  createEntry({