@praxisui/dynamic-fields 6.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)',
@@ -2614,6 +2619,7 @@ class SimpleBaseSelectComponent extends SimpleBaseInputComponent {
2614
2619
  global = inject(GlobalConfigService);
2615
2620
  // Dependency cascade support (Phase 1)
2616
2621
  dependencySub = null;
2622
+ remoteSelectionHydrationSub = null;
2617
2623
  hasLoadedOnce = false;
2618
2624
  /** Options filtered according to the current `searchTerm` */
2619
2625
  filteredOptions = computed(() => {
@@ -3145,18 +3151,24 @@ class SimpleBaseSelectComponent extends SimpleBaseInputComponent {
3145
3151
  if (searchTerm && !this.optionSource()) {
3146
3152
  filter[this.optionLabelKey()] = searchTerm;
3147
3153
  }
3154
+ const includeIds = page === 0 ? this.currentIncludeIds() : undefined;
3148
3155
  this.loading.set(true);
3149
3156
  const request$ = this.optionSource()
3150
3157
  ? this.crudService.filterOptionSourceOptions(this.optionSource().key, filter, { pageNumber: page, pageSize: this.pageSize() }, {
3151
3158
  search: searchTerm || undefined,
3152
3159
  includeIds: this.optionSource().includeIds
3153
- ? this.currentIncludeIds()
3160
+ ? includeIds
3154
3161
  : undefined,
3155
3162
  })
3156
- : this.crudService.filter(filter, {
3157
- pageNumber: page,
3158
- pageSize: this.pageSize(),
3159
- });
3163
+ : includeIds?.length
3164
+ ? this.crudService.filter(filter, {
3165
+ pageNumber: page,
3166
+ pageSize: this.pageSize(),
3167
+ }, { includeIds })
3168
+ : this.crudService.filter(filter, {
3169
+ pageNumber: page,
3170
+ pageSize: this.pageSize(),
3171
+ });
3160
3172
  request$
3161
3173
  .pipe(takeUntilDestroyed(this.destroyRef), take(1))
3162
3174
  .subscribe({
@@ -3240,6 +3252,45 @@ class SimpleBaseSelectComponent extends SimpleBaseInputComponent {
3240
3252
  // Recreate subscriptions when the bound control instance changes
3241
3253
  queueMicrotask(() => this.setupDependencies());
3242
3254
  }, ...(ngDevMode ? [{ debugName: "_depEffect" }] : []));
3255
+ /** Rebind hydration for remote selected values when FormControl changes */
3256
+ _remoteSelectionEffect = effect(() => {
3257
+ const _ctrl = this.formControl();
3258
+ const _path = this.resourcePath();
3259
+ queueMicrotask(() => this.setupRemoteSelectionHydration());
3260
+ }, ...(ngDevMode ? [{ debugName: "_remoteSelectionEffect" }] : []));
3261
+ setupRemoteSelectionHydration() {
3262
+ if (this.remoteSelectionHydrationSub) {
3263
+ try {
3264
+ this.remoteSelectionHydrationSub.unsubscribe();
3265
+ }
3266
+ catch { }
3267
+ this.remoteSelectionHydrationSub = null;
3268
+ }
3269
+ if (!this.resourcePath()) {
3270
+ return;
3271
+ }
3272
+ const control = this.control();
3273
+ this.remoteSelectionHydrationSub = control.valueChanges
3274
+ .pipe(startWith(control.value), takeUntilDestroyed(this.destroyRef))
3275
+ .subscribe(() => {
3276
+ this.ensureCurrentValueLoaded();
3277
+ });
3278
+ }
3279
+ ensureCurrentValueLoaded() {
3280
+ if (!this.resourcePath() || this.loading()) {
3281
+ return;
3282
+ }
3283
+ const selected = this.currentIncludeIds();
3284
+ if (!selected.length) {
3285
+ return;
3286
+ }
3287
+ const hasAllSelectedOptions = selected.every((value) => this.options().some((candidate) => this.areSelectValuesEqual(candidate.value, value)));
3288
+ if (hasAllSelectedOptions) {
3289
+ return;
3290
+ }
3291
+ this.pageIndex.set(0);
3292
+ this.loadOptions(0, this.searchTerm(), this.filterCriteria());
3293
+ }
3243
3294
  /** (Re)configure dependency observers based on metadata and current FormGroup */
3244
3295
  setupDependencies() {
3245
3296
  // Teardown previous subscriptions if any
@@ -4388,7 +4439,7 @@ class MaterialCheckboxGroupComponent extends SimpleBaseSelectComponent {
4388
4439
  : !!mappedOptions?.length;
4389
4440
  super.setSelectMetadata({
4390
4441
  ...matMetadata,
4391
- options: mappedOptions,
4442
+ options: mappedOptions ?? [],
4392
4443
  multiple,
4393
4444
  searchable: matMetadata.searchable,
4394
4445
  selectAll: matMetadata.selectAll,
@@ -7558,168 +7609,234 @@ function createNumberInputComponentMetadata(locale = 'en-US') {
7558
7609
  return createWave1ComponentDocMeta(PDX_NUMBER_INPUT_EDITORIAL_DESCRIPTOR, locale);
7559
7610
  }
7560
7611
 
7561
- // =============================================================================
7562
- // INTERFACES ESPECÍFICAS DO TEXT-INPUT (herda BaseValidationConfig)
7563
- // =============================================================================
7564
- class TextInputComponent extends SimpleBaseInputComponent {
7565
- // =============================================================================
7566
- // OUTPUTS ESPECÍFICOS
7567
- // =============================================================================
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. */
7568
7621
  validationChange = output();
7569
7622
  // Praxis Field States
7570
7623
  readonlyMode = false;
7571
7624
  disabledMode = false;
7572
7625
  visible = true;
7573
7626
  presentationMode = false;
7574
- isReadonlyEffective() {
7575
- const st = computeEffectiveState(this.metadataAsField(), {
7576
- readonlyMode: this.readonlyMode,
7577
- presentationMode: this.presentationMode,
7578
- });
7579
- return st.readonly;
7580
- }
7581
- // =============================================================================
7582
- // COMPUTED PROPERTIES (validação e classes herdadas da base)
7583
- // =============================================================================
7584
- // Todas as computed properties (CSS classes, validação, Material Design) estão na base class
7585
- // =============================================================================
7586
- // LIFECYCLE
7587
- // =============================================================================
7627
+ allowedCharactersValidator = Validators.pattern(/^[0-9()+\-\s]*$/);
7628
+ semanticPhoneValidator = (control) => this.validatePhoneControl(control);
7588
7629
  ngOnInit() {
7589
- super.ngOnInit(); // Chama a inicialização da classe base (já inclui setupValidators)
7590
- try {
7591
- 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;
7592
7646
  }
7593
- catch (error) {
7594
- 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;
7595
7655
  }
7596
- }
7597
- onComponentInit() {
7598
- // Inicialização específica do text-input
7599
- const meta = this.metadata();
7600
- if (meta) {
7601
- // Inicializar valor padrão se definido
7602
- if (meta.defaultValue !== undefined && this.control().value == null) {
7603
- this.control().setValue(meta.defaultValue, { emitEvent: false });
7604
- }
7656
+ this.setValue(modelValue, { emitEvent: true });
7657
+ if (input && input.value !== displayValue) {
7658
+ input.value = displayValue;
7605
7659
  }
7606
7660
  }
7607
- /**
7608
- * Adiciona classes CSS específicas do text-input
7609
- */
7610
- getSpecificCssClasses() {
7611
- return ['pdx-text-input'];
7612
- }
7613
- // =============================================================================
7614
- // EVENT HANDLERS (inherited from base, pode ser customizado se necessário)
7615
- // =============================================================================
7616
- // =============================================================================
7617
- // MÉTODOS PÚBLICOS ESPECÍFICOS
7618
- // =============================================================================
7619
- /**
7620
- * Reset do campo
7621
- */
7622
- resetField() {
7623
- const meta = this.metadata();
7624
- const defaultValue = meta?.defaultValue ?? null;
7625
- this.setValue(defaultValue, { emitEvent: false });
7626
- // Reset estados via base class
7627
- this.componentState.update((state) => ({
7628
- ...state,
7629
- touched: false,
7630
- dirty: false,
7631
- }));
7632
- this.fieldState.update((state) => ({
7633
- ...state,
7634
- value: defaultValue,
7635
- valid: true,
7636
- errors: null,
7637
- }));
7638
- const control = this.control();
7639
- control.markAsPristine();
7640
- 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);
7641
7677
  }
7642
- /**
7643
- * Força validação do campo (override para emitir evento)
7644
- */
7645
7678
  async validateField() {
7646
7679
  const errors = await super.validateField();
7647
7680
  this.validationChange.emit(errors);
7648
7681
  return errors;
7649
7682
  }
7650
- /**
7651
- * Define metadata e aplica configurações
7652
- */
7653
- setInputMetadata(metadata) {
7654
- this.setMetadata(metadata); // Base class já reaplica validators
7655
- // Reaplicar defaultValue dinamicamente sem recriar componente
7656
- const ctrl = this.control();
7657
- if (ctrl && ctrl.pristine && (ctrl.value == null || ctrl.value === '') &&
7658
- metadata.defaultValue !== undefined) {
7659
- ctrl.setValue(metadata.defaultValue, { emitEvent: false });
7660
- }
7661
- }
7662
- showClear() {
7663
- const cfg = this.clearButtonConfig();
7664
- if (cfg === undefined || cfg === null)
7665
- return false;
7666
- const enabled = typeof cfg === 'boolean' ? cfg : cfg.enabled === true;
7667
- if (!enabled)
7668
- return false;
7669
- if (this.disabledMode || this.isReadonlyEffective() || this.presentationMode)
7670
- return false;
7671
- if (cfg && typeof cfg === 'object' && cfg.showOnlyWhenFilled) {
7672
- const v = this.control().value;
7673
- return v !== null && v !== undefined && String(v) !== '';
7674
- }
7675
- return true;
7683
+ getSpecificCssClasses() {
7684
+ return ['pdx-phone-input'];
7676
7685
  }
7677
- onClearClick() {
7678
- // Prefer empty string for text input
7679
- this.setValue('', { emitEvent: true });
7680
- const control = this.control();
7681
- control.markAsDirty();
7682
- control.markAsTouched();
7686
+ /** Applies strongly typed metadata to the component. */
7687
+ setInputMetadata(metadata) {
7688
+ this.setMetadata(metadata);
7689
+ this.syncPhoneValidators();
7690
+ this.applyPhoneDisplayValue();
7683
7691
  }
7684
- // Material error state matcher derived from metadata configuration
7685
7692
  errorStateMatcher() {
7686
7693
  return getErrorStateMatcherForField(this.metadata());
7687
7694
  }
7688
- clearButtonConfig() {
7689
- const raw = this.metadataRecord()?.['clearButton'];
7690
- if (typeof raw === 'boolean') {
7691
- return raw;
7692
- }
7693
- if (raw && typeof raw === 'object') {
7694
- 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;
7695
7706
  }
7696
- return null;
7707
+ return metadata;
7697
7708
  }
7698
- metadataRecord() {
7709
+ phoneMetadata() {
7699
7710
  const metadata = this.metadata();
7700
7711
  return metadata && typeof metadata === 'object'
7701
7712
  ? metadata
7702
- : null;
7713
+ : {};
7703
7714
  }
7704
- metadataAsField() {
7705
- const metadata = this.metadata();
7706
- 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) {
7707
7730
  return null;
7708
7731
  }
7709
- 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;
7710
7756
  }
7711
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TextInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
7712
- 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: [
7713
7831
  {
7714
7832
  provide: NG_VALUE_ACCESSOR,
7715
- useExisting: forwardRef(() => TextInputComponent),
7833
+ useExisting: forwardRef(() => PhoneInputComponent),
7716
7834
  multi: true,
7717
7835
  },
7718
7836
  ], usesInheritance: true, ngImport: i0, template: `
7719
7837
  <mat-form-field
7720
7838
  [appearance]="materialAppearance()"
7721
7839
  [color]="materialColor()"
7722
- floatLabel="auto"
7723
7840
  [class]="componentCssClasses()"
7724
7841
  [floatLabel]="floatLabelBehavior()"
7725
7842
  [subscriptSizing]="metadata()?.materialDesign?.subscriptSizing || 'fixed'"
@@ -7739,16 +7856,12 @@ class TextInputComponent extends SimpleBaseInputComponent {
7739
7856
  shouldShowPlaceholder && placeholder ? placeholder : null
7740
7857
  "
7741
7858
  [required]="metadata()?.required || false"
7742
- [type]="inputType()"
7743
- [autocomplete]="metadata()?.autocomplete || 'off'"
7744
- [spellcheck]="metadata()?.spellcheck ?? true"
7745
7859
  [readonly]="isReadonlyEffective()"
7746
- [attr.aria-disabled]="disabledMode ? 'true' : null"
7747
- [maxlength]="metadata()?.maxLength || null"
7748
- [minlength]="metadata()?.minLength || null"
7860
+ [autocomplete]="metadata()?.autocomplete || 'tel'"
7861
+ [type]="inputType()"
7749
7862
  [attr.aria-label]="!label && placeholder ? placeholder : null"
7863
+ [attr.aria-disabled]="disabledMode ? 'true' : null"
7750
7864
  [attr.aria-required]="metadata()?.required ? 'true' : 'false'"
7751
- [attr.aria-invalid]="ariaInvalidAttribute()"
7752
7865
  [matTooltip]="tooltipEnabled() ? errorMessage() : null"
7753
7866
  [matTooltipDisabled]="!(tooltipEnabled() && !!errorMessage())"
7754
7867
  [matTooltipPosition]="tooltipPosition()"
@@ -7759,12 +7872,13 @@ class TextInputComponent extends SimpleBaseInputComponent {
7759
7872
  matSuffix
7760
7873
  [color]="iconPalette(metadata()?.suffixIconColor)"
7761
7874
  [style.color]="iconCustomColor(metadata()?.suffixIconColor)"
7875
+ [praxisIcon]="metadata()!.suffixIcon"
7762
7876
  [matTooltip]="metadata()?.suffixIconTooltip || null"
7763
7877
  [attr.aria-label]="metadata()?.suffixIconAriaLabel || null"
7764
- [praxisIcon]="metadata()!.suffixIcon"
7765
7878
  ></mat-icon>
7766
7879
  }
7767
7880
 
7881
+
7768
7882
  @if (showClear()) {
7769
7883
  <button
7770
7884
  mat-icon-button
@@ -7792,26 +7906,18 @@ class TextInputComponent extends SimpleBaseInputComponent {
7792
7906
  metadata()!.hint
7793
7907
  }}</mat-hint>
7794
7908
  }
7795
-
7796
- @if (metadata()?.showCharacterCount && metadata()?.maxLength) {
7797
- <mat-hint align="end">
7798
- {{ (control().value || '').length }} /
7799
- {{ metadata()!.maxLength }}
7800
- </mat-hint>
7801
- }
7802
7909
  </mat-form-field>
7803
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "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: MatFormFieldModule }, { 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: "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: "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.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i1$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
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"] }] });
7804
7911
  }
7805
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TextInputComponent, decorators: [{
7912
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PhoneInputComponent, decorators: [{
7806
7913
  type: Component,
7807
7914
  args: [{
7808
- selector: 'pdx-text-input',
7915
+ selector: 'pdx-phone-input',
7809
7916
  standalone: true,
7810
7917
  template: `
7811
7918
  <mat-form-field
7812
7919
  [appearance]="materialAppearance()"
7813
7920
  [color]="materialColor()"
7814
- floatLabel="auto"
7815
7921
  [class]="componentCssClasses()"
7816
7922
  [floatLabel]="floatLabelBehavior()"
7817
7923
  [subscriptSizing]="metadata()?.materialDesign?.subscriptSizing || 'fixed'"
@@ -7831,16 +7937,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7831
7937
  shouldShowPlaceholder && placeholder ? placeholder : null
7832
7938
  "
7833
7939
  [required]="metadata()?.required || false"
7834
- [type]="inputType()"
7835
- [autocomplete]="metadata()?.autocomplete || 'off'"
7836
- [spellcheck]="metadata()?.spellcheck ?? true"
7837
7940
  [readonly]="isReadonlyEffective()"
7838
- [attr.aria-disabled]="disabledMode ? 'true' : null"
7839
- [maxlength]="metadata()?.maxLength || null"
7840
- [minlength]="metadata()?.minLength || null"
7941
+ [autocomplete]="metadata()?.autocomplete || 'tel'"
7942
+ [type]="inputType()"
7841
7943
  [attr.aria-label]="!label && placeholder ? placeholder : null"
7944
+ [attr.aria-disabled]="disabledMode ? 'true' : null"
7842
7945
  [attr.aria-required]="metadata()?.required ? 'true' : 'false'"
7843
- [attr.aria-invalid]="ariaInvalidAttribute()"
7844
7946
  [matTooltip]="tooltipEnabled() ? errorMessage() : null"
7845
7947
  [matTooltipDisabled]="!(tooltipEnabled() && !!errorMessage())"
7846
7948
  [matTooltipPosition]="tooltipPosition()"
@@ -7851,9 +7953,395 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7851
7953
  matSuffix
7852
7954
  [color]="iconPalette(metadata()?.suffixIconColor)"
7853
7955
  [style.color]="iconCustomColor(metadata()?.suffixIconColor)"
7956
+ [praxisIcon]="metadata()!.suffixIcon"
7854
7957
  [matTooltip]="metadata()?.suffixIconTooltip || null"
7855
7958
  [attr.aria-label]="metadata()?.suffixIconAriaLabel || null"
7856
- [praxisIcon]="metadata()!.suffixIcon"
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()"
8240
+ [matTooltip]="tooltipEnabled() ? errorMessage() : null"
8241
+ [matTooltipDisabled]="!(tooltipEnabled() && !!errorMessage())"
8242
+ [matTooltipPosition]="tooltipPosition()"
8243
+ />
8244
+
8245
+ @if (metadata()?.suffixIcon) {
8246
+ <mat-icon
8247
+ matSuffix
8248
+ [color]="iconPalette(metadata()?.suffixIconColor)"
8249
+ [style.color]="iconCustomColor(metadata()?.suffixIconColor)"
8250
+ [matTooltip]="metadata()?.suffixIconTooltip || null"
8251
+ [attr.aria-label]="metadata()?.suffixIconAriaLabel || null"
8252
+ [praxisIcon]="metadata()!.suffixIcon"
8253
+ ></mat-icon>
8254
+ }
8255
+
8256
+ @if (showClear()) {
8257
+ <button
8258
+ mat-icon-button
8259
+ matSuffix
8260
+ type="button"
8261
+ (click)="onClearClick()"
8262
+ [disabled]="disabledMode || isReadonlyEffective() || presentationMode"
8263
+ [matTooltip]="clearActionTooltip()"
8264
+ [attr.aria-label]="clearActionAriaLabel()"
8265
+ >
8266
+ <mat-icon
8267
+ [color]="iconPalette(metadata()?.clearButton?.iconColor)"
8268
+ [style.color]="iconCustomColor(metadata()?.clearButton?.iconColor)"
8269
+ [praxisIcon]="metadata()?.clearButton?.icon || 'mi:clear'"
8270
+ ></mat-icon>
8271
+ </button>
8272
+ }
8273
+
8274
+ @if (hasValidationError()) {
8275
+ <mat-error>{{ errorMessage() }}</mat-error>
8276
+ }
8277
+
8278
+ @if (metadata()?.hint && !hasValidationError()) {
8279
+ <mat-hint [align]="metadata()?.hintAlign || 'start'">{{
8280
+ metadata()!.hint
8281
+ }}</mat-hint>
8282
+ }
8283
+
8284
+ @if (metadata()?.showCharacterCount && metadata()?.maxLength) {
8285
+ <mat-hint align="end">
8286
+ {{ (control().value || '').length }} /
8287
+ {{ metadata()!.maxLength }}
8288
+ </mat-hint>
8289
+ }
8290
+ </mat-form-field>
8291
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "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: MatFormFieldModule }, { 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: "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: "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.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i1$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
8292
+ }
8293
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TextInputComponent, decorators: [{
8294
+ type: Component,
8295
+ args: [{
8296
+ selector: 'pdx-text-input',
8297
+ standalone: true,
8298
+ template: `
8299
+ <mat-form-field
8300
+ [appearance]="materialAppearance()"
8301
+ [color]="materialColor()"
8302
+ floatLabel="auto"
8303
+ [class]="componentCssClasses()"
8304
+ [floatLabel]="floatLabelBehavior()"
8305
+ [subscriptSizing]="metadata()?.materialDesign?.subscriptSizing || 'fixed'"
8306
+ [hideRequiredMarker]="metadata()?.materialDesign?.hideRequiredMarker || false"
8307
+ >
8308
+ <mat-label>{{ label }}</mat-label>
8309
+
8310
+ @if (metadata()?.prefixIcon) {
8311
+ <mat-icon matPrefix [color]="iconPalette(metadata()?.prefixIconColor)" [style.color]="iconCustomColor(metadata()?.prefixIconColor)" [praxisIcon]="metadata()!.prefixIcon"></mat-icon>
8312
+ }
8313
+
8314
+ <input
8315
+ matInput
8316
+ [formControl]="control()"
8317
+ [errorStateMatcher]="errorStateMatcher()"
8318
+ [attr.placeholder]="
8319
+ shouldShowPlaceholder && placeholder ? placeholder : null
8320
+ "
8321
+ [required]="metadata()?.required || false"
8322
+ [type]="inputType()"
8323
+ [autocomplete]="metadata()?.autocomplete || 'off'"
8324
+ [spellcheck]="metadata()?.spellcheck ?? true"
8325
+ [readonly]="isReadonlyEffective()"
8326
+ [attr.aria-disabled]="disabledMode ? 'true' : null"
8327
+ [maxlength]="metadata()?.maxLength || null"
8328
+ [minlength]="metadata()?.minLength || null"
8329
+ [attr.aria-label]="!label && placeholder ? placeholder : null"
8330
+ [attr.aria-required]="metadata()?.required ? 'true' : 'false'"
8331
+ [attr.aria-invalid]="ariaInvalidAttribute()"
8332
+ [matTooltip]="tooltipEnabled() ? errorMessage() : null"
8333
+ [matTooltipDisabled]="!(tooltipEnabled() && !!errorMessage())"
8334
+ [matTooltipPosition]="tooltipPosition()"
8335
+ />
8336
+
8337
+ @if (metadata()?.suffixIcon) {
8338
+ <mat-icon
8339
+ matSuffix
8340
+ [color]="iconPalette(metadata()?.suffixIconColor)"
8341
+ [style.color]="iconCustomColor(metadata()?.suffixIconColor)"
8342
+ [matTooltip]="metadata()?.suffixIconTooltip || null"
8343
+ [attr.aria-label]="metadata()?.suffixIconAriaLabel || null"
8344
+ [praxisIcon]="metadata()!.suffixIcon"
7857
8345
  ></mat-icon>
7858
8346
  }
7859
8347
 
@@ -7953,6 +8441,7 @@ function createTextInputComponentMetadata(locale = 'en-US') {
7953
8441
 
7954
8442
  const PRAXIS_DYNAMIC_FIELDS_WAVE_1_COMPONENT_METADATA = [
7955
8443
  createTextInputComponentMetadata(),
8444
+ createPhoneInputComponentMetadata(),
7956
8445
  createNumberInputComponentMetadata(),
7957
8446
  createMaterialSelectComponentMetadata(),
7958
8447
  createMaterialTextareaComponentMetadata(),
@@ -7966,6 +8455,7 @@ const PRAXIS_DYNAMIC_FIELDS_WAVE_1_COMPONENT_METADATA = [
7966
8455
  ];
7967
8456
  const PRAXIS_DYNAMIC_FIELDS_EDITORIAL_WAVE_1 = [
7968
8457
  PDX_TEXT_INPUT_EDITORIAL_DESCRIPTOR,
8458
+ PDX_PHONE_INPUT_EDITORIAL_DESCRIPTOR,
7969
8459
  PDX_NUMBER_INPUT_EDITORIAL_DESCRIPTOR,
7970
8460
  PDX_MATERIAL_SELECT_EDITORIAL_DESCRIPTOR,
7971
8461
  PDX_MATERIAL_TEXTAREA_EDITORIAL_DESCRIPTOR,
@@ -10701,19 +11191,10 @@ class DynamicFieldLoaderDirective {
10701
11191
  */
10702
11192
  applyGlobalStates() {
10703
11193
  this.shellRefs.forEach((shellRef) => {
10704
- if (this.readonlyMode !== null) {
10705
- shellRef.instance.readonlyMode = this.readonlyMode === true;
10706
- }
10707
- if (this.disabledMode !== null) {
10708
- shellRef.instance.disabledMode = this.disabledMode === true;
10709
- }
10710
- if (this.presentationMode !== null) {
10711
- shellRef.instance.presentationMode = this.presentationMode === true;
10712
- }
10713
- if (this.visible !== null) {
10714
- // visível por padrão; apenas aplicar quando definido
10715
- shellRef.instance.visible = this.visible !== false ? true : false;
10716
- }
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;
10717
11198
  shellRef.changeDetectorRef.detectChanges();
10718
11199
  const compRef = this.componentRefs.get(shellRef.instance.field?.name);
10719
11200
  if (compRef) {
@@ -43816,254 +44297,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
43816
44297
  type: Input
43817
44298
  }] } });
43818
44299
 
43819
- /**
43820
- * Specialized input component for telephone numbers.
43821
- *
43822
- * Renders a `<mat-form-field>` wrapping an `<input type="tel">` element with
43823
- * Material styling. Built on top of `SimpleBaseInputComponent` to leverage
43824
- * reactive forms integration, hint/error messaging and validation hooks.
43825
- */
43826
- class PhoneInputComponent extends SimpleBaseInputComponent {
43827
- /** Emits whenever validation state changes. */
43828
- validationChange = output();
43829
- // Praxis Field States
43830
- readonlyMode = false;
43831
- disabledMode = false;
43832
- visible = true;
43833
- presentationMode = false;
43834
- ngOnInit() {
43835
- super.ngOnInit();
43836
- this.control().addValidators(Validators.pattern(/^[0-9()+\-\s]*$/));
43837
- }
43838
- async validateField() {
43839
- const errors = await super.validateField();
43840
- this.validationChange.emit(errors);
43841
- return errors;
43842
- }
43843
- getSpecificCssClasses() {
43844
- return ['pdx-phone-input'];
43845
- }
43846
- /** Applies strongly typed metadata to the component. */
43847
- setInputMetadata(metadata) {
43848
- this.setMetadata(metadata);
43849
- }
43850
- errorStateMatcher() {
43851
- return getErrorStateMatcherForField(this.metadata());
43852
- }
43853
- isReadonlyEffective() {
43854
- const st = computeEffectiveState(this.metadataAsField(), {
43855
- readonlyMode: this.readonlyMode,
43856
- presentationMode: this.presentationMode,
43857
- });
43858
- return st.readonly;
43859
- }
43860
- metadataAsField() {
43861
- const metadata = this.metadata();
43862
- if (!metadata || typeof metadata !== 'object') {
43863
- return null;
43864
- }
43865
- return metadata;
43866
- }
43867
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PhoneInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
43868
- 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: [
43869
- {
43870
- provide: NG_VALUE_ACCESSOR,
43871
- useExisting: forwardRef(() => PhoneInputComponent),
43872
- multi: true,
43873
- },
43874
- ], usesInheritance: true, ngImport: i0, template: `
43875
- <mat-form-field
43876
- [appearance]="materialAppearance()"
43877
- [color]="materialColor()"
43878
- [class]="componentCssClasses()"
43879
- [floatLabel]="floatLabelBehavior()"
43880
- [subscriptSizing]="metadata()?.materialDesign?.subscriptSizing || 'fixed'"
43881
- [hideRequiredMarker]="metadata()?.materialDesign?.hideRequiredMarker || false"
43882
- >
43883
- <mat-label>{{ label }}</mat-label>
43884
-
43885
- @if (metadata()?.prefixIcon) {
43886
- <mat-icon matPrefix [color]="iconPalette(metadata()?.prefixIconColor)" [style.color]="iconCustomColor(metadata()?.prefixIconColor)" [praxisIcon]="metadata()!.prefixIcon"></mat-icon>
43887
- }
43888
-
43889
- <input
43890
- matInput
43891
- [formControl]="control()"
43892
- [errorStateMatcher]="errorStateMatcher()"
43893
- [attr.placeholder]="
43894
- shouldShowPlaceholder && placeholder ? placeholder : null
43895
- "
43896
- [required]="metadata()?.required || false"
43897
- [readonly]="isReadonlyEffective()"
43898
- [autocomplete]="metadata()?.autocomplete || 'tel'"
43899
- [type]="inputType()"
43900
- [attr.aria-label]="!label && placeholder ? placeholder : null"
43901
- [attr.aria-disabled]="disabledMode ? 'true' : null"
43902
- [attr.aria-required]="metadata()?.required ? 'true' : 'false'"
43903
- [matTooltip]="tooltipEnabled() ? errorMessage() : null"
43904
- [matTooltipDisabled]="!(tooltipEnabled() && !!errorMessage())"
43905
- [matTooltipPosition]="tooltipPosition()"
43906
- />
43907
-
43908
- @if (metadata()?.suffixIcon) {
43909
- <mat-icon
43910
- matSuffix
43911
- [color]="iconPalette(metadata()?.suffixIconColor)"
43912
- [style.color]="iconCustomColor(metadata()?.suffixIconColor)"
43913
- [praxisIcon]="metadata()!.suffixIcon"
43914
- [matTooltip]="metadata()?.suffixIconTooltip || null"
43915
- [attr.aria-label]="metadata()?.suffixIconAriaLabel || null"
43916
- ></mat-icon>
43917
- }
43918
-
43919
-
43920
- @if (showClear()) {
43921
- <button
43922
- mat-icon-button
43923
- matSuffix
43924
- type="button"
43925
- (click)="onClearClick()"
43926
- [disabled]="disabledMode || isReadonlyEffective() || presentationMode"
43927
- [matTooltip]="clearActionTooltip()"
43928
- [attr.aria-label]="clearActionAriaLabel()"
43929
- >
43930
- <mat-icon
43931
- [color]="iconPalette(metadata()?.clearButton?.iconColor)"
43932
- [style.color]="iconCustomColor(metadata()?.clearButton?.iconColor)"
43933
- [praxisIcon]="metadata()?.clearButton?.icon || 'mi:clear'"
43934
- ></mat-icon>
43935
- </button>
43936
- }
43937
-
43938
- @if (hasValidationError()) {
43939
- <mat-error>{{ errorMessage() }}</mat-error>
43940
- }
43941
-
43942
- @if (metadata()?.hint && !hasValidationError()) {
43943
- <mat-hint [align]="metadata()?.hintAlign || 'start'">{{
43944
- metadata()!.hint
43945
- }}</mat-hint>
43946
- }
43947
- </mat-form-field>
43948
- `, 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"] }] });
43949
- }
43950
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PhoneInputComponent, decorators: [{
43951
- type: Component,
43952
- args: [{
43953
- selector: 'pdx-phone-input',
43954
- standalone: true,
43955
- template: `
43956
- <mat-form-field
43957
- [appearance]="materialAppearance()"
43958
- [color]="materialColor()"
43959
- [class]="componentCssClasses()"
43960
- [floatLabel]="floatLabelBehavior()"
43961
- [subscriptSizing]="metadata()?.materialDesign?.subscriptSizing || 'fixed'"
43962
- [hideRequiredMarker]="metadata()?.materialDesign?.hideRequiredMarker || false"
43963
- >
43964
- <mat-label>{{ label }}</mat-label>
43965
-
43966
- @if (metadata()?.prefixIcon) {
43967
- <mat-icon matPrefix [color]="iconPalette(metadata()?.prefixIconColor)" [style.color]="iconCustomColor(metadata()?.prefixIconColor)" [praxisIcon]="metadata()!.prefixIcon"></mat-icon>
43968
- }
43969
-
43970
- <input
43971
- matInput
43972
- [formControl]="control()"
43973
- [errorStateMatcher]="errorStateMatcher()"
43974
- [attr.placeholder]="
43975
- shouldShowPlaceholder && placeholder ? placeholder : null
43976
- "
43977
- [required]="metadata()?.required || false"
43978
- [readonly]="isReadonlyEffective()"
43979
- [autocomplete]="metadata()?.autocomplete || 'tel'"
43980
- [type]="inputType()"
43981
- [attr.aria-label]="!label && placeholder ? placeholder : null"
43982
- [attr.aria-disabled]="disabledMode ? 'true' : null"
43983
- [attr.aria-required]="metadata()?.required ? 'true' : 'false'"
43984
- [matTooltip]="tooltipEnabled() ? errorMessage() : null"
43985
- [matTooltipDisabled]="!(tooltipEnabled() && !!errorMessage())"
43986
- [matTooltipPosition]="tooltipPosition()"
43987
- />
43988
-
43989
- @if (metadata()?.suffixIcon) {
43990
- <mat-icon
43991
- matSuffix
43992
- [color]="iconPalette(metadata()?.suffixIconColor)"
43993
- [style.color]="iconCustomColor(metadata()?.suffixIconColor)"
43994
- [praxisIcon]="metadata()!.suffixIcon"
43995
- [matTooltip]="metadata()?.suffixIconTooltip || null"
43996
- [attr.aria-label]="metadata()?.suffixIconAriaLabel || null"
43997
- ></mat-icon>
43998
- }
43999
-
44000
-
44001
- @if (showClear()) {
44002
- <button
44003
- mat-icon-button
44004
- matSuffix
44005
- type="button"
44006
- (click)="onClearClick()"
44007
- [disabled]="disabledMode || isReadonlyEffective() || presentationMode"
44008
- [matTooltip]="clearActionTooltip()"
44009
- [attr.aria-label]="clearActionAriaLabel()"
44010
- >
44011
- <mat-icon
44012
- [color]="iconPalette(metadata()?.clearButton?.iconColor)"
44013
- [style.color]="iconCustomColor(metadata()?.clearButton?.iconColor)"
44014
- [praxisIcon]="metadata()?.clearButton?.icon || 'mi:clear'"
44015
- ></mat-icon>
44016
- </button>
44017
- }
44018
-
44019
- @if (hasValidationError()) {
44020
- <mat-error>{{ errorMessage() }}</mat-error>
44021
- }
44022
-
44023
- @if (metadata()?.hint && !hasValidationError()) {
44024
- <mat-hint [align]="metadata()?.hintAlign || 'start'">{{
44025
- metadata()!.hint
44026
- }}</mat-hint>
44027
- }
44028
- </mat-form-field>
44029
- `,
44030
- imports: [
44031
- MatButtonModule,
44032
- CommonModule,
44033
- MatFormFieldModule,
44034
- MatInputModule,
44035
- MatIconModule,
44036
- MatTooltipModule,
44037
- PraxisIconDirective,
44038
- ReactiveFormsModule,
44039
- ],
44040
- providers: [
44041
- {
44042
- provide: NG_VALUE_ACCESSOR,
44043
- useExisting: forwardRef(() => PhoneInputComponent),
44044
- multi: true,
44045
- },
44046
- ],
44047
- host: {
44048
- '[class]': 'componentCssClasses()',
44049
- '[class.praxis-disabled]': 'disabledMode',
44050
- '[style.display]': 'visible ? null : "none"',
44051
- '[attr.aria-hidden]': 'visible ? null : "true"',
44052
- '[attr.data-field-type]': '"phone"',
44053
- '[attr.data-field-name]': 'metadata()?.name',
44054
- '[attr.data-component-id]': 'componentId()',
44055
- },
44056
- }]
44057
- }], propDecorators: { validationChange: [{ type: i0.Output, args: ["validationChange"] }], readonlyMode: [{
44058
- type: Input
44059
- }], disabledMode: [{
44060
- type: Input
44061
- }], visible: [{
44062
- type: Input
44063
- }], presentationMode: [{
44064
- type: Input
44065
- }] } });
44066
-
44067
44300
  /**
44068
44301
  * Specialized input component for HTML time inputs.
44069
44302
  *
@@ -48808,26 +49041,7 @@ const PDX_PASSWORD_INPUT_COMPONENT_METADATA = {
48808
49041
  lib: '@praxisui/dynamic-fields',
48809
49042
  };
48810
49043
 
48811
- const PDX_PHONE_INPUT_COMPONENT_METADATA = {
48812
- id: 'pdx-phone-input',
48813
- selector: 'pdx-phone-input',
48814
- component: PhoneInputComponent,
48815
- friendlyName: 'Telefone (input)',
48816
- description: 'Campo de telefone com máscara e validação.',
48817
- icon: 'call',
48818
- inputs: [
48819
- { name: 'metadata', type: 'MaterialPhoneMetadata', description: 'Configuração do campo' },
48820
- { name: 'readonlyMode', type: 'boolean', default: false, description: 'Somente leitura' },
48821
- { name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilitado' },
48822
- { name: 'visible', type: 'boolean', default: true, description: 'Visibilidade' },
48823
- { name: 'presentationMode', type: 'boolean', default: false, description: 'Modo apresentação' },
48824
- ],
48825
- outputs: [
48826
- { name: 'validationChange', type: 'ValidationErrors | null', description: 'Emite ao validar' },
48827
- ],
48828
- tags: ['widget', 'field', 'input', 'material', 'phone'],
48829
- lib: '@praxisui/dynamic-fields',
48830
- };
49044
+ const PDX_PHONE_INPUT_COMPONENT_METADATA = createPhoneInputComponentMetadata();
48831
49045
 
48832
49046
  const PDX_PRELOAD_STATUS_COMPONENT_METADATA = {
48833
49047
  id: 'pdx-preload-status',
@@ -49724,7 +49938,7 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49724
49938
  track: 'primary-form',
49725
49939
  family: 'text',
49726
49940
  friendlyName: 'Phone input',
49727
- description: 'Specialized phone field.',
49941
+ description: 'Specialized phone field with country-aware masking and validation.',
49728
49942
  tags: ['text', 'phone', 'formatted'],
49729
49943
  valueShape: 'string',
49730
49944
  recommendedWhen: ['phone', 'contact'],
@@ -49732,6 +49946,51 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49732
49946
  dataSourceKind: 'local',
49733
49947
  status: 'partial-docs',
49734
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
+ ],
49735
49994
  }),
49736
49995
  createEntry({
49737
49996
  id: 'url-input',
@@ -49762,6 +50021,38 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49762
50021
  dataSourceKind: 'local',
49763
50022
  status: 'partial-docs',
49764
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
+ ],
49765
50056
  }),
49766
50057
  createWave1Entry({
49767
50058
  id: 'number-input',
@@ -49794,6 +50085,57 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49794
50085
  stateRecipes: [
49795
50086
  createDefaultSeedRecipe(18450.75, 'Seeded default preview to expose formatted monetary rendering at first glance.'),
49796
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
+ ],
49797
50139
  }),
49798
50140
  createEntry({
49799
50141
  id: 'slider',
@@ -49833,7 +50175,7 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49833
50175
  friendlyName: 'Price range',
49834
50176
  description: 'Currency range with dedicated semantics.',
49835
50177
  tags: ['number', 'currency', 'range'],
49836
- valueShape: '{ min, max }',
50178
+ valueShape: '{ minPrice, maxPrice }',
49837
50179
  recommendedWhen: ['price range', 'salary band', 'minimum and maximum budget'],
49838
50180
  avoidWhen: ['single monetary value'],
49839
50181
  dataSourceKind: 'local',
@@ -49841,6 +50183,54 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49841
50183
  status: 'partial-docs',
49842
50184
  apiPath: jsonApiPath('material-price-range/pdx-material-price-range.json-api.md'),
49843
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
+ ],
49844
50234
  }),
49845
50235
  createEntry({
49846
50236
  id: 'rating',
@@ -49899,6 +50289,7 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
49899
50289
  apiPath: jsonApiPath('material-date-range/pdx-material-date-range.json-api.md'),
49900
50290
  stateRecipes: [
49901
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.'),
49902
50293
  ],
49903
50294
  }),
49904
50295
  createEntry({