@praxisui/dynamic-fields 9.0.0-beta.17 → 9.0.0-beta.18

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.
@@ -130,6 +130,7 @@ const PRAXIS_DYNAMIC_FIELDS_EN_US = {
130
130
  'praxis.dynamicFields.validation.unique': 'Value is already in use',
131
131
  'praxis.dynamicFields.validation.fileType': 'File type is not allowed',
132
132
  'praxis.dynamicFields.validation.maxFileSize': 'File exceeds the maximum size',
133
+ 'praxis.dynamicFields.validation.maxFiles': 'Select at most {{count}} file(s)',
133
134
  'praxis.dynamicFields.validation.minWords': 'Text has too few words',
134
135
  'praxis.dynamicFields.validation.conditional': 'Validation rule not satisfied',
135
136
  'praxis.dynamicFields.validation.invalidValue': 'Invalid value',
@@ -142,6 +143,16 @@ const PRAXIS_DYNAMIC_FIELDS_EN_US = {
142
143
  'praxis.dynamicFields.actions.clear.tooltip': 'Clear',
143
144
  'praxis.dynamicFields.actions.clear.ariaLabel': 'Clear field',
144
145
  'praxis.dynamicFields.actions.clear.ariaLabelWithLabel': 'Clear {{label}}',
146
+ 'praxis.dynamicFields.fileUpload.emptySummary': 'No file selected',
147
+ 'praxis.dynamicFields.fileUpload.multipleSummary': '{{count}} files selected',
148
+ 'praxis.dynamicFields.fileUpload.clearSelection': 'Clear',
149
+ 'praxis.dynamicFields.fileUpload.clearSelectionAriaLabel': 'Clear selected file',
150
+ 'praxis.dynamicFields.fileUpload.selectedSummaryAriaLabel': 'Selected file',
151
+ 'praxis.dynamicFields.fileUpload.previewAlt': 'Selected image preview',
152
+ 'praxis.dynamicFields.fileUpload.sizeUnitBytes': 'B',
153
+ 'praxis.dynamicFields.fileUpload.sizeUnitKb': 'KB',
154
+ 'praxis.dynamicFields.fileUpload.sizeUnitMb': 'MB',
155
+ 'praxis.dynamicFields.fileUpload.sizeUnitGb': 'GB',
145
156
  'praxis.dynamicFields.array.add': 'Add item',
146
157
  'praxis.dynamicFields.array.remove': 'Remove',
147
158
  'praxis.dynamicFields.array.empty': 'No items registered.',
@@ -509,6 +520,7 @@ const PRAXIS_DYNAMIC_FIELDS_PT_BR = {
509
520
  'praxis.dynamicFields.validation.unique': 'Valor já utilizado',
510
521
  'praxis.dynamicFields.validation.fileType': 'Tipo de arquivo não permitido',
511
522
  'praxis.dynamicFields.validation.maxFileSize': 'Arquivo excede o tamanho máximo',
523
+ 'praxis.dynamicFields.validation.maxFiles': 'Selecione no máximo {{count}} arquivo(s)',
512
524
  'praxis.dynamicFields.validation.minWords': 'Texto com poucas palavras',
513
525
  'praxis.dynamicFields.validation.conditional': 'Regra de validação não atendida',
514
526
  'praxis.dynamicFields.validation.invalidValue': 'Valor inválido',
@@ -521,6 +533,16 @@ const PRAXIS_DYNAMIC_FIELDS_PT_BR = {
521
533
  'praxis.dynamicFields.actions.clear.tooltip': 'Limpar',
522
534
  'praxis.dynamicFields.actions.clear.ariaLabel': 'Limpar campo',
523
535
  'praxis.dynamicFields.actions.clear.ariaLabelWithLabel': 'Limpar {{label}}',
536
+ 'praxis.dynamicFields.fileUpload.emptySummary': 'Nenhum arquivo selecionado',
537
+ 'praxis.dynamicFields.fileUpload.multipleSummary': '{{count}} arquivos selecionados',
538
+ 'praxis.dynamicFields.fileUpload.clearSelection': 'Limpar',
539
+ 'praxis.dynamicFields.fileUpload.clearSelectionAriaLabel': 'Limpar arquivo selecionado',
540
+ 'praxis.dynamicFields.fileUpload.selectedSummaryAriaLabel': 'Arquivo selecionado',
541
+ 'praxis.dynamicFields.fileUpload.previewAlt': 'Pré-visualização da imagem selecionada',
542
+ 'praxis.dynamicFields.fileUpload.sizeUnitBytes': 'B',
543
+ 'praxis.dynamicFields.fileUpload.sizeUnitKb': 'KB',
544
+ 'praxis.dynamicFields.fileUpload.sizeUnitMb': 'MB',
545
+ 'praxis.dynamicFields.fileUpload.sizeUnitGb': 'GB',
524
546
  'praxis.dynamicFields.array.add': 'Adicionar item',
525
547
  'praxis.dynamicFields.array.remove': 'Remover',
526
548
  'praxis.dynamicFields.array.empty': 'Nenhum item cadastrado.',
@@ -775,7 +797,7 @@ const PRAXIS_DYNAMIC_FIELDS_PT_BR = {
775
797
  'praxis.dynamicFields.entityLookup.action.create': 'Novo',
776
798
  'praxis.dynamicFields.entityLookup.action.clear': 'Limpar',
777
799
  'praxis.dynamicFields.entityLookup.sort.label': 'Ordenar por',
778
- 'praxis.dynamicFields.entityLookup.sort.default': 'Padrao',
800
+ 'praxis.dynamicFields.entityLookup.sort.default': 'Padrão',
779
801
  'praxis.dynamicFields.entityLookup.filter.clearAll': 'Limpar filtros',
780
802
  'praxis.dynamicFields.entityLookup.dialog.open': 'Busca avançada',
781
803
  'praxis.dynamicFields.entityLookup.dialog.openSupporting': 'Refine a busca com filtros, ordenacao e contexto.',
@@ -812,7 +834,7 @@ const PRAXIS_DYNAMIC_FIELDS_PT_BR = {
812
834
  'praxis.dynamicFields.entityLookup.dialog.preview.legacy': 'Valor legado',
813
835
  'praxis.dynamicFields.entityLookup.dialog.preview.blocked': 'Bloqueado',
814
836
  'praxis.dynamicFields.entityLookup.dialog.preview.selectable': 'Selecionavel',
815
- 'praxis.dynamicFields.entityLookup.selectionLimitReached': 'Limite de {max} selecoes atingido.',
837
+ 'praxis.dynamicFields.entityLookup.selectionLimitReached': 'Limite de {max} seleções atingido.',
816
838
  'praxis.dynamicFields.entityLookup.action.removeSelection': 'Remover {label}',
817
839
  'praxis.dynamicFields.entityLookup.noResults': 'Nenhuma entidade encontrada para os filtros atuais.',
818
840
  'praxis.dynamicFields.entityLookup.unmetDependencies': 'Preencha os campos dependentes antes de buscar.',
@@ -864,7 +886,7 @@ const PRAXIS_DYNAMIC_FIELDS_PT_BR = {
864
886
  'praxis.dynamicFields.editorial.avatar.inputs.emphasis.description': 'Estado de destaque visual usado para avatares selecionados, principais ou de atenção sem alterar valores submetidos.',
865
887
  'praxis.dynamicFields.editorial.avatar.inputs.emphasisColor.description': 'Token de cor usado pelo anel ou halo de destaque.',
866
888
  'praxis.dynamicFields.editorial.avatar.inputs.emphasisLabel.description': 'Rotulo acessivel que descreve o significado do destaque visual.',
867
- 'praxis.dynamicFields.editorial.avatar.inputs.initialsMaxLength.description': 'Numero maximo de iniciais renderizadas',
889
+ 'praxis.dynamicFields.editorial.avatar.inputs.initialsMaxLength.description': 'Número máximo de iniciais renderizadas',
868
890
  'praxis.dynamicFields.editorial.avatar.inputs.tooltip.description': 'Texto do tooltip',
869
891
  'praxis.dynamicFields.editorial.avatar.inputs.ariaLabel.description': 'Rótulo ARIA',
870
892
  'praxis.dynamicFields.editorial.avatar.inputs.class.description': 'Classes CSS adicionais no host',
@@ -2980,8 +3002,8 @@ class SimpleBaseButtonComponent {
2980
3002
  // Adicionar confirmação se configurada
2981
3003
  if (action && meta.confirmDialog) {
2982
3004
  action.confirmation = {
2983
- title: meta.confirmDialog.title || this.tDynamicFields('button.confirmTitle', 'Confirmar acao'),
2984
- message: meta.confirmDialog.message || this.tDynamicFields('button.confirmMessage', 'Deseja continuar com esta acao?'),
3005
+ title: meta.confirmDialog.title || this.tDynamicFields('button.confirmTitle', 'Confirmar ação'),
3006
+ message: meta.confirmDialog.message || this.tDynamicFields('button.confirmMessage', 'Deseja continuar com esta ação?'),
2985
3007
  confirmLabel: meta.confirmDialog.confirmLabel || this.tDynamicFields('dialog.confirm', 'Confirmar'),
2986
3008
  cancelLabel: meta.confirmDialog.cancelLabel || this.tDynamicFields('dialog.cancel', 'Cancelar'),
2987
3009
  };
@@ -8809,7 +8831,7 @@ const LoggerPresets = {
8809
8831
  }
8810
8832
  };
8811
8833
 
8812
- const EMPTY_DISPLAY = '-- Nao informado';
8834
+ const EMPTY_DISPLAY = '-- Não informado';
8813
8835
  function isISODate(v) {
8814
8836
  return /^\d{4}-\d{2}-\d{2}$/.test(v);
8815
8837
  }
@@ -9356,7 +9378,7 @@ function formatBooleanPresentation(value, locale) {
9356
9378
  function resolveBooleanLabels(locale) {
9357
9379
  const normalized = String(locale || '').trim().toLowerCase();
9358
9380
  if (normalized.startsWith('pt')) {
9359
- return ['Sim', 'Nao'];
9381
+ return ['Sim', 'Não'];
9360
9382
  }
9361
9383
  if (normalized.startsWith('es')) {
9362
9384
  return ['Si', 'No'];
@@ -13117,7 +13139,7 @@ class InlineEntityLookupComponent extends MaterialAsyncSelectComponent {
13117
13139
  }
13118
13140
  selectionLimitReachedText(maxSelections) {
13119
13141
  const max = Number(maxSelections ?? 0);
13120
- const template = this.tDynamicFields('entityLookup.selectionLimitReached', 'Limite de {max} selecoes atingido.', { max: Number.isFinite(max) && max > 0 ? max : 0 });
13142
+ const template = this.tDynamicFields('entityLookup.selectionLimitReached', 'Limite de {max} seleções atingido.', { max: Number.isFinite(max) && max > 0 ? max : 0 });
13121
13143
  return template.replace('{max}', String(Number.isFinite(max) && max > 0 ? max : 0));
13122
13144
  }
13123
13145
  onDetailAction(event, item) {
@@ -13287,7 +13309,7 @@ class InlineEntityLookupComponent extends MaterialAsyncSelectComponent {
13287
13309
  }
13288
13310
  resetOptionText() {
13289
13311
  const custom = String(this.metadata()?.resetLabel || '').trim();
13290
- return custom || 'Limpar selecao';
13312
+ return custom || 'Limpar seleção';
13291
13313
  }
13292
13314
  showQuickClear() {
13293
13315
  const clearCfg = this.currentMetadata().clearButton;
@@ -13391,7 +13413,7 @@ class InlineEntityLookupComponent extends MaterialAsyncSelectComponent {
13391
13413
  this.retry();
13392
13414
  }
13393
13415
  errorStateLabel() {
13394
- return this.error() || this.tDynamicFields('select.loadOptionsError', 'Erro ao carregar opcoes.');
13416
+ return this.error() || this.tDynamicFields('select.loadOptionsError', 'Erro ao carregar opções.');
13395
13417
  }
13396
13418
  dependencyStateLabel() {
13397
13419
  const dependencyLabels = this.missingDependencyLabels();
@@ -13462,7 +13484,7 @@ class InlineEntityLookupComponent extends MaterialAsyncSelectComponent {
13462
13484
  return this.tDynamicFields('entityLookup.sort.label', 'Ordenar por');
13463
13485
  }
13464
13486
  sortDefaultOptionText() {
13465
- return this.tDynamicFields('entityLookup.sort.default', 'Padrao');
13487
+ return this.tDynamicFields('entityLookup.sort.default', 'Padrão');
13466
13488
  }
13467
13489
  clearAllFiltersText() {
13468
13490
  return this.tDynamicFields('entityLookup.filter.clearAll', 'Limpar filtros');
@@ -17635,11 +17657,11 @@ function createInlinePhoneComponentMetadata(locale = 'en-US') {
17635
17657
  }
17636
17658
 
17637
17659
  /**
17638
- * Placeholder component for file upload fields.
17660
+ * Baseline file upload field.
17639
17661
  *
17640
- * Currently provides a simple `<input type="file">` element and basic
17641
- * ControlValueAccessor integration. A full-featured implementation will
17642
- * be provided in future iterations.
17662
+ * This component owns local file selection, validation and preview. Persisting
17663
+ * the selected file remains a host/backend concern; use @praxisui/files-upload
17664
+ * when the flow needs presign, progress, conflict policy or storage events.
17643
17665
  */
17644
17666
  class MaterialFileUploadComponent extends SimpleBaseInputComponent {
17645
17667
  // Praxis Field States
@@ -17647,13 +17669,55 @@ class MaterialFileUploadComponent extends SimpleBaseInputComponent {
17647
17669
  disabledMode = false;
17648
17670
  visible = true;
17649
17671
  presentationMode = false;
17672
+ selectedFiles = signal([], ...(ngDevMode ? [{ debugName: "selectedFiles" }] : /* istanbul ignore next */ []));
17673
+ previewUrl = signal(null, ...(ngDevMode ? [{ debugName: "previewUrl" }] : /* istanbul ignore next */ []));
17674
+ externalValueSyncSubscription = new Subscription();
17675
+ selectedSummaryText = computed(() => {
17676
+ const files = this.selectedFiles();
17677
+ if (!files.length) {
17678
+ return this.dynamicFieldsText('fileUpload.emptySummary', 'No file selected');
17679
+ }
17680
+ if (files.length === 1) {
17681
+ return files[0].name;
17682
+ }
17683
+ return this.dynamicFieldsText('fileUpload.multipleSummary', '{{count}} files selected', {
17684
+ count: files.length,
17685
+ });
17686
+ }, ...(ngDevMode ? [{ debugName: "selectedSummaryText" }] : /* istanbul ignore next */ []));
17687
+ selectedDetailText = computed(() => {
17688
+ const files = this.selectedFiles();
17689
+ if (!files.length)
17690
+ return '';
17691
+ const total = files.reduce((sum, file) => sum + file.size, 0);
17692
+ return this.formatBytes(total);
17693
+ }, ...(ngDevMode ? [{ debugName: "selectedDetailText" }] : /* istanbul ignore next */ []));
17650
17694
  /**
17651
17695
  * Updates the control value when the user selects a file.
17652
17696
  */
17653
17697
  onFileSelected(event) {
17654
17698
  const input = event.target;
17655
- const file = input.files?.[0] ?? null;
17656
- this.setValue(file);
17699
+ const files = Array.from(input.files ?? []);
17700
+ if (!files.length) {
17701
+ this.clearSelectedFiles({ resetInput: false });
17702
+ return;
17703
+ }
17704
+ const selected = this.allowsMultiple() ? files : files.slice(0, 1);
17705
+ const validationError = this.validateSelectedFiles(selected);
17706
+ if (validationError) {
17707
+ this.clearPreview();
17708
+ this.selectedFiles.set([]);
17709
+ this.setValue(this.allowsMultiple() ? [] : null, { emitEvent: true });
17710
+ this.applyFileError(validationError);
17711
+ input.value = '';
17712
+ return;
17713
+ }
17714
+ this.clearFileErrors();
17715
+ this.selectedFiles.set(selected);
17716
+ this.updatePreview(selected);
17717
+ this.setValue(this.allowsMultiple() ? selected : selected[0], { emitEvent: true });
17718
+ const control = this.control();
17719
+ control.markAsDirty();
17720
+ control.markAsTouched();
17657
17721
  }
17658
17722
  /**
17659
17723
  * Exposes metadata setter for integration with dynamic form loader.
@@ -17669,8 +17733,71 @@ class MaterialFileUploadComponent extends SimpleBaseInputComponent {
17669
17733
  allowsMultiple() {
17670
17734
  return this.metadataRecord()?.['multiple'] === true;
17671
17735
  }
17736
+ imagePreviewEnabled() {
17737
+ const metadata = this.metadataRecord();
17738
+ if (metadata?.['imagePreview'] === true)
17739
+ return true;
17740
+ if (metadata?.['preview'] === 'image')
17741
+ return true;
17742
+ const accept = this.fileAccept()?.toLowerCase() ?? '';
17743
+ return accept.includes('image/');
17744
+ }
17672
17745
  isInteractionDisabled() {
17673
- return this.disabledMode || this.readonlyMode || this.presentationMode;
17746
+ return this.isInteractionBlockedByState();
17747
+ }
17748
+ clearSelectedFiles(options = {}) {
17749
+ this.clearPreview();
17750
+ this.selectedFiles.set([]);
17751
+ this.clearFileErrors();
17752
+ this.setValue(this.allowsMultiple() ? [] : null, { emitEvent: true });
17753
+ const control = this.control();
17754
+ control.markAsDirty();
17755
+ control.markAsTouched();
17756
+ if (options.resetInput !== false) {
17757
+ const input = this.elementRef.nativeElement.querySelector('input[type="file"]');
17758
+ if (input)
17759
+ input.value = '';
17760
+ }
17761
+ }
17762
+ clearLabel() {
17763
+ return this.dynamicFieldsText('fileUpload.clearSelection', 'Clear');
17764
+ }
17765
+ clearAriaLabel() {
17766
+ return this.dynamicFieldsText('fileUpload.clearSelectionAriaLabel', 'Clear selected file');
17767
+ }
17768
+ selectedSummaryAriaLabel() {
17769
+ return this.dynamicFieldsText('fileUpload.selectedSummaryAriaLabel', 'Selected file');
17770
+ }
17771
+ fileInputDescribedBy() {
17772
+ const ids = [
17773
+ this.metadata()?.ariaDescribedby,
17774
+ this.hasValidationError() ? this.errorId() : null,
17775
+ this.metadata()?.hint && !this.hasValidationError() ? this.hintId() : null,
17776
+ ]
17777
+ .map((id) => (typeof id === 'string' ? id.trim() : ''))
17778
+ .filter(Boolean);
17779
+ return ids.length ? ids.join(' ') : null;
17780
+ }
17781
+ hintId() {
17782
+ return `${this.componentId()}-hint`;
17783
+ }
17784
+ errorId() {
17785
+ return `${this.componentId()}-error`;
17786
+ }
17787
+ previewAltText() {
17788
+ const label = this.metadataRecord()?.['previewAlt'];
17789
+ if (typeof label === 'string' && label.trim())
17790
+ return label.trim();
17791
+ return this.dynamicFieldsText('fileUpload.previewAlt', 'Selected image preview');
17792
+ }
17793
+ writeValue(value) {
17794
+ super.writeValue(value);
17795
+ this.syncSelectedFilesFromValue(value);
17796
+ }
17797
+ ngOnDestroy() {
17798
+ this.externalValueSyncSubscription.unsubscribe();
17799
+ this.clearPreview();
17800
+ super.ngOnDestroy();
17674
17801
  }
17675
17802
  /**
17676
17803
  * CSS classes specific to the file upload wrapper.
@@ -17678,12 +17805,168 @@ class MaterialFileUploadComponent extends SimpleBaseInputComponent {
17678
17805
  getSpecificCssClasses() {
17679
17806
  return ['pdx-material-file-upload'];
17680
17807
  }
17808
+ onActiveControlChanged(previousControl, currentControl) {
17809
+ super.onActiveControlChanged(previousControl, currentControl);
17810
+ this.externalValueSyncSubscription.unsubscribe();
17811
+ this.externalValueSyncSubscription = currentControl.valueChanges.subscribe((value) => {
17812
+ this.syncSelectedFilesFromValue(value);
17813
+ });
17814
+ this.syncSelectedFilesFromValue(currentControl.value);
17815
+ }
17681
17816
  metadataRecord() {
17682
17817
  const metadata = this.metadata();
17683
17818
  return metadata && typeof metadata === 'object'
17684
17819
  ? metadata
17685
17820
  : null;
17686
17821
  }
17822
+ updatePreview(files) {
17823
+ this.clearPreview();
17824
+ if (!this.imagePreviewEnabled())
17825
+ return;
17826
+ const image = files.find((file) => this.isImageFile(file));
17827
+ if (!image || typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') {
17828
+ return;
17829
+ }
17830
+ this.previewUrl.set(URL.createObjectURL(image));
17831
+ }
17832
+ clearPreview() {
17833
+ const current = this.previewUrl();
17834
+ if (current && typeof URL !== 'undefined' && typeof URL.revokeObjectURL === 'function') {
17835
+ URL.revokeObjectURL(current);
17836
+ }
17837
+ this.previewUrl.set(null);
17838
+ }
17839
+ validateSelectedFiles(files) {
17840
+ const maxFileSize = this.maxFileSize();
17841
+ const maxFiles = this.maxFiles();
17842
+ if (maxFiles !== null && files.length > maxFiles) {
17843
+ return {
17844
+ key: 'maxFiles',
17845
+ message: this.dynamicFieldsText('validation.maxFiles', 'Too many files selected', {
17846
+ count: maxFiles,
17847
+ }),
17848
+ };
17849
+ }
17850
+ for (const file of files) {
17851
+ if (!this.isAcceptedFile(file)) {
17852
+ return {
17853
+ key: 'fileType',
17854
+ message: this.dynamicFieldsText('validation.fileType', 'File type is not allowed'),
17855
+ };
17856
+ }
17857
+ if (maxFileSize !== null && file.size > maxFileSize) {
17858
+ return {
17859
+ key: 'maxFileSize',
17860
+ message: this.dynamicFieldsText('validation.maxFileSize', 'File exceeds the maximum size'),
17861
+ };
17862
+ }
17863
+ }
17864
+ return null;
17865
+ }
17866
+ isAcceptedFile(file) {
17867
+ const accept = this.fileAccept();
17868
+ if (!accept)
17869
+ return true;
17870
+ const fileName = file.name.toLowerCase();
17871
+ const fileType = file.type.toLowerCase();
17872
+ return accept
17873
+ .split(',')
17874
+ .map((token) => token.trim().toLowerCase())
17875
+ .filter(Boolean)
17876
+ .some((token) => {
17877
+ if (token === '*/*')
17878
+ return true;
17879
+ if (token === 'image/*')
17880
+ return fileType.startsWith('image/') || this.hasKnownImageExtension(fileName);
17881
+ if (token.endsWith('/*'))
17882
+ return fileType.startsWith(token.slice(0, -1));
17883
+ if (token.startsWith('.'))
17884
+ return fileName.endsWith(token);
17885
+ return fileType === token;
17886
+ });
17887
+ }
17888
+ maxFileSize() {
17889
+ const raw = this.metadataRecord()?.['maxFileSize'];
17890
+ const value = typeof raw === 'number' ? raw : Number(raw);
17891
+ return Number.isFinite(value) && value > 0 ? value : null;
17892
+ }
17893
+ maxFiles() {
17894
+ const raw = this.metadataRecord()?.['maxFiles'];
17895
+ const value = typeof raw === 'number' ? raw : Number(raw);
17896
+ return Number.isInteger(value) && value > 0 ? value : null;
17897
+ }
17898
+ applyFileError(error) {
17899
+ const control = this.control();
17900
+ const current = control.errors ?? {};
17901
+ control.setErrors({
17902
+ ...current,
17903
+ [error.key]: { message: error.message },
17904
+ });
17905
+ control.markAsDirty();
17906
+ control.markAsTouched();
17907
+ }
17908
+ clearFileErrors() {
17909
+ const control = this.control();
17910
+ const current = control.errors;
17911
+ if (!current)
17912
+ return;
17913
+ const { fileType: _fileType, maxFileSize: _maxFileSize, maxFiles: _maxFiles, ...rest } = current;
17914
+ control.setErrors(Object.keys(rest).length ? rest : null);
17915
+ }
17916
+ syncSelectedFilesFromValue(value) {
17917
+ const files = this.extractFilesFromValue(value);
17918
+ this.selectedFiles.set(files);
17919
+ this.updatePreview(files);
17920
+ }
17921
+ extractFilesFromValue(value) {
17922
+ if (!value)
17923
+ return [];
17924
+ if (this.isFile(value))
17925
+ return [value];
17926
+ if (Array.isArray(value))
17927
+ return value.filter((item) => this.isFile(item));
17928
+ return [];
17929
+ }
17930
+ isFile(value) {
17931
+ return typeof File !== 'undefined' && value instanceof File;
17932
+ }
17933
+ isImageFile(file) {
17934
+ return file.type.toLowerCase().startsWith('image/') || this.hasKnownImageExtension(file.name.toLowerCase());
17935
+ }
17936
+ hasKnownImageExtension(fileName) {
17937
+ return [
17938
+ '.apng',
17939
+ '.avif',
17940
+ '.bmp',
17941
+ '.gif',
17942
+ '.jpeg',
17943
+ '.jpg',
17944
+ '.png',
17945
+ '.svg',
17946
+ '.webp',
17947
+ ].some((extension) => fileName.endsWith(extension));
17948
+ }
17949
+ formatBytes(bytes) {
17950
+ const units = [
17951
+ this.dynamicFieldsText('fileUpload.sizeUnitBytes', 'B'),
17952
+ this.dynamicFieldsText('fileUpload.sizeUnitKb', 'KB'),
17953
+ this.dynamicFieldsText('fileUpload.sizeUnitMb', 'MB'),
17954
+ this.dynamicFieldsText('fileUpload.sizeUnitGb', 'GB'),
17955
+ ];
17956
+ if (!Number.isFinite(bytes) || bytes <= 0)
17957
+ return `0 ${units[0]}`;
17958
+ let value = bytes;
17959
+ let index = 0;
17960
+ while (value >= 1024 && index < units.length - 1) {
17961
+ value /= 1024;
17962
+ index += 1;
17963
+ }
17964
+ const rounded = value >= 10 || index === 0 ? Math.round(value) : Math.round(value * 10) / 10;
17965
+ return `${rounded} ${units[index]}`;
17966
+ }
17967
+ dynamicFieldsText(keySuffix, fallback, params) {
17968
+ return this.i18n.t(`praxis.dynamicFields.${keySuffix}`, params, fallback, 'dynamicFields');
17969
+ }
17687
17970
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: MaterialFileUploadComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
17688
17971
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: MaterialFileUploadComponent, isStandalone: true, selector: "pdx-material-file-upload", inputs: { readonlyMode: "readonlyMode", disabledMode: "disabledMode", visible: "visible", presentationMode: "presentationMode" }, host: { properties: { "class": "componentCssClasses()", "class.praxis-disabled": "disabledMode", "style.display": "visible ? null : \"none\"", "attr.aria-hidden": "visible ? null : \"true\"", "attr.data-field-type": "\"file-upload\"", "attr.data-field-name": "metadata()?.name", "attr.data-component-id": "componentId()" } }, providers: [
17689
17972
  {
@@ -17706,24 +17989,51 @@ class MaterialFileUploadComponent extends SimpleBaseInputComponent {
17706
17989
  [attr.accept]="fileAccept()"
17707
17990
  [attr.multiple]="allowsMultiple() ? '' : null"
17708
17991
  [disabled]="isInteractionDisabled()"
17709
- [attr.aria-disabled]="disabledMode ? 'true' : null"
17992
+ [attr.aria-disabled]="isInteractionDisabled() ? 'true' : null"
17993
+ [attr.aria-describedby]="fileInputDescribedBy()"
17994
+ [attr.aria-invalid]="hasValidationError() ? 'true' : null"
17995
+ [attr.aria-label]="inputAriaLabel()"
17710
17996
  />
17997
+ @if (selectedFiles().length) {
17998
+ <div
17999
+ class="pdx-file-upload__selection"
18000
+ aria-live="polite"
18001
+ [attr.aria-label]="selectedSummaryAriaLabel()"
18002
+ >
18003
+ @if (previewUrl()) {
18004
+ <img
18005
+ class="pdx-file-upload__preview"
18006
+ [src]="previewUrl()!"
18007
+ [attr.alt]="previewAltText()"
18008
+ />
18009
+ }
18010
+ <div class="pdx-file-upload__summary">
18011
+ <span class="pdx-file-upload__summary-title">{{ selectedSummaryText() }}</span>
18012
+ <span class="pdx-file-upload__summary-detail">{{ selectedDetailText() }}</span>
18013
+ </div>
18014
+ <button
18015
+ class="pdx-file-upload__clear"
18016
+ type="button"
18017
+ [disabled]="isInteractionDisabled()"
18018
+ [attr.aria-label]="clearAriaLabel()"
18019
+ (click)="clearSelectedFiles()"
18020
+ >
18021
+ {{ clearLabel() }}
18022
+ </button>
18023
+ </div>
18024
+ }
17711
18025
  @if (hasValidationError()) {
17712
- <div class="error">{{ errorMessage() }}</div>
18026
+ <div class="error" [attr.id]="errorId()">{{ errorMessage() }}</div>
17713
18027
  }
17714
18028
  @if (metadata()?.hint && !hasValidationError()) {
17715
- <div class="hint">{{ metadata()!.hint }}</div>
18029
+ <div class="hint" [attr.id]="hintId()">{{ metadata()!.hint }}</div>
17716
18030
  }
17717
18031
  </div>
17718
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
18032
+ `, isInline: true, styles: [".pdx-file-upload{display:flex;flex-direction:column;gap:8px}.pdx-file-upload label{font-size:12px;font-weight:500;line-height:1.25;color:var(--md-sys-color-on-surface, rgba(0, 0, 0, .87))}.pdx-file-upload input[type=file]{max-width:100%}.pdx-file-upload__selection{display:flex;align-items:center;gap:10px;min-width:0;padding:8px;border:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .16));border-radius:8px;background:var(--md-sys-color-surface-container-low, rgba(0, 0, 0, .03))}.pdx-file-upload__preview{width:48px;height:48px;border-radius:9999px;object-fit:cover;flex:0 0 48px;border:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .16))}.pdx-file-upload__summary{display:flex;min-width:0;flex:1 1 auto;flex-direction:column;gap:2px}.pdx-file-upload__summary-title,.pdx-file-upload__summary-detail{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pdx-file-upload__summary-title{font-size:13px;font-weight:600;color:var(--md-sys-color-on-surface, rgba(0, 0, 0, .87))}.pdx-file-upload__summary-detail,.hint{font-size:12px;color:var(--md-sys-color-on-surface-variant, rgba(0, 0, 0, .6))}.pdx-file-upload__clear{border:0;background:transparent;color:var(--md-sys-color-primary, #6750a4);font:inherit;font-size:12px;font-weight:600;cursor:pointer;padding:6px}.pdx-file-upload__clear:disabled{cursor:default;opacity:.5}.error{color:var(--md-sys-color-error, #b3261e);font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
17719
18033
  }
17720
18034
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: MaterialFileUploadComponent, decorators: [{
17721
18035
  type: Component,
17722
- args: [{
17723
- selector: 'pdx-material-file-upload',
17724
- standalone: true,
17725
- imports: [ReactiveFormsModule, MatTooltipModule],
17726
- template: `
18036
+ args: [{ selector: 'pdx-material-file-upload', standalone: true, imports: [ReactiveFormsModule, MatTooltipModule], template: `
17727
18037
  <div
17728
18038
  class="pdx-file-upload"
17729
18039
  [matTooltip]="tooltipEnabled() ? errorMessage() : null"
@@ -17738,24 +18048,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
17738
18048
  [attr.accept]="fileAccept()"
17739
18049
  [attr.multiple]="allowsMultiple() ? '' : null"
17740
18050
  [disabled]="isInteractionDisabled()"
17741
- [attr.aria-disabled]="disabledMode ? 'true' : null"
18051
+ [attr.aria-disabled]="isInteractionDisabled() ? 'true' : null"
18052
+ [attr.aria-describedby]="fileInputDescribedBy()"
18053
+ [attr.aria-invalid]="hasValidationError() ? 'true' : null"
18054
+ [attr.aria-label]="inputAriaLabel()"
17742
18055
  />
18056
+ @if (selectedFiles().length) {
18057
+ <div
18058
+ class="pdx-file-upload__selection"
18059
+ aria-live="polite"
18060
+ [attr.aria-label]="selectedSummaryAriaLabel()"
18061
+ >
18062
+ @if (previewUrl()) {
18063
+ <img
18064
+ class="pdx-file-upload__preview"
18065
+ [src]="previewUrl()!"
18066
+ [attr.alt]="previewAltText()"
18067
+ />
18068
+ }
18069
+ <div class="pdx-file-upload__summary">
18070
+ <span class="pdx-file-upload__summary-title">{{ selectedSummaryText() }}</span>
18071
+ <span class="pdx-file-upload__summary-detail">{{ selectedDetailText() }}</span>
18072
+ </div>
18073
+ <button
18074
+ class="pdx-file-upload__clear"
18075
+ type="button"
18076
+ [disabled]="isInteractionDisabled()"
18077
+ [attr.aria-label]="clearAriaLabel()"
18078
+ (click)="clearSelectedFiles()"
18079
+ >
18080
+ {{ clearLabel() }}
18081
+ </button>
18082
+ </div>
18083
+ }
17743
18084
  @if (hasValidationError()) {
17744
- <div class="error">{{ errorMessage() }}</div>
18085
+ <div class="error" [attr.id]="errorId()">{{ errorMessage() }}</div>
17745
18086
  }
17746
18087
  @if (metadata()?.hint && !hasValidationError()) {
17747
- <div class="hint">{{ metadata()!.hint }}</div>
18088
+ <div class="hint" [attr.id]="hintId()">{{ metadata()!.hint }}</div>
17748
18089
  }
17749
18090
  </div>
17750
- `,
17751
- providers: [
18091
+ `, providers: [
17752
18092
  {
17753
18093
  provide: NG_VALUE_ACCESSOR,
17754
18094
  useExisting: forwardRef(() => MaterialFileUploadComponent),
17755
18095
  multi: true,
17756
18096
  },
17757
- ],
17758
- host: {
18097
+ ], host: {
17759
18098
  '[class]': 'componentCssClasses()',
17760
18099
  '[class.praxis-disabled]': 'disabledMode',
17761
18100
  '[style.display]': 'visible ? null : "none"',
@@ -17763,8 +18102,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
17763
18102
  '[attr.data-field-type]': '"file-upload"',
17764
18103
  '[attr.data-field-name]': 'metadata()?.name',
17765
18104
  '[attr.data-component-id]': 'componentId()',
17766
- },
17767
- }]
18105
+ }, styles: [".pdx-file-upload{display:flex;flex-direction:column;gap:8px}.pdx-file-upload label{font-size:12px;font-weight:500;line-height:1.25;color:var(--md-sys-color-on-surface, rgba(0, 0, 0, .87))}.pdx-file-upload input[type=file]{max-width:100%}.pdx-file-upload__selection{display:flex;align-items:center;gap:10px;min-width:0;padding:8px;border:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .16));border-radius:8px;background:var(--md-sys-color-surface-container-low, rgba(0, 0, 0, .03))}.pdx-file-upload__preview{width:48px;height:48px;border-radius:9999px;object-fit:cover;flex:0 0 48px;border:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .16))}.pdx-file-upload__summary{display:flex;min-width:0;flex:1 1 auto;flex-direction:column;gap:2px}.pdx-file-upload__summary-title,.pdx-file-upload__summary-detail{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pdx-file-upload__summary-title{font-size:13px;font-weight:600;color:var(--md-sys-color-on-surface, rgba(0, 0, 0, .87))}.pdx-file-upload__summary-detail,.hint{font-size:12px;color:var(--md-sys-color-on-surface-variant, rgba(0, 0, 0, .6))}.pdx-file-upload__clear{border:0;background:transparent;color:var(--md-sys-color-primary, #6750a4);font:inherit;font-size:12px;font-weight:600;cursor:pointer;padding:6px}.pdx-file-upload__clear:disabled{cursor:default;opacity:.5}.error{color:var(--md-sys-color-error, #b3261e);font-size:12px}\n"] }]
17768
18106
  }], propDecorators: { readonlyMode: [{
17769
18107
  type: Input
17770
18108
  }], disabledMode: [{
@@ -43619,7 +43957,7 @@ class InlineDistanceRadiusComponent extends InlineRangeSliderComponent {
43619
43957
  const subtitle = String(md.distanceSubtitle ?? md.inlinePanelSubtitle ?? md.inlineSubtitle ?? md.hint ?? '').trim();
43620
43958
  if (subtitle.length)
43621
43959
  return subtitle;
43622
- return this.distanceText('inlineDistance.panelSubtitle', 'Visualizacao radial com controle de raio e unidade');
43960
+ return this.distanceText('inlineDistance.panelSubtitle', 'Visualização radial com controle de raio e unidade');
43623
43961
  }
43624
43962
  unitToggleAriaLabel() {
43625
43963
  return this.distanceText('inlineDistance.unitToggleAriaLabel', 'Unidade de distancia atual: {{unit}}', {
@@ -59414,7 +59752,7 @@ const PDX_INLINE_DATE_RANGE_COMPONENT_METADATA = {
59414
59752
  selector: 'pdx-inline-date-range',
59415
59753
  component: InlineDateRangeComponent,
59416
59754
  friendlyName: 'Inline Date Range',
59417
- description: 'Intervalo de datas compacto para barras de filtro dinamico, com autosize e presets rápidos no overlay do calendário.',
59755
+ description: 'Intervalo de datas compacto para barras de filtro dinâmico, com autosize e presets rápidos no overlay do calendário.',
59418
59756
  icon: 'date_range',
59419
59757
  inputs: [
59420
59758
  { name: 'metadata', type: 'MaterialDateRangeMetadata', description: 'Configuração do campo' },
@@ -59432,10 +59770,10 @@ const PDX_INLINE_TIME_COMPONENT_METADATA = {
59432
59770
  selector: 'pdx-inline-time',
59433
59771
  component: InlineTimeComponent,
59434
59772
  friendlyName: 'Inline Time',
59435
- description: 'Timepicker compacto para barras de filtro dinamico, com largura por conteudo e selecao inline em lista ou colunas Hor/Min/Seg.',
59773
+ description: 'Timepicker compacto para barras de filtro dinâmico, com largura por conteúdo e seleção inline em lista ou colunas Hor/Min/Seg.',
59436
59774
  icon: 'schedule',
59437
59775
  inputs: [
59438
- { name: 'metadata', type: 'MaterialTimepickerMetadata', description: 'Configuracao do campo' },
59776
+ { name: 'metadata', type: 'MaterialTimepickerMetadata', description: 'Configuração do campo' },
59439
59777
  { name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
59440
59778
  { name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
59441
59779
  { name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
@@ -59453,7 +59791,7 @@ const PDX_INLINE_TIME_RANGE_COMPONENT_METADATA = {
59453
59791
  description: 'Intervalo de horário compacto para barras de filtro dinâmico, com pill responsiva, slider de faixa, atalhos rápidos e popover não-modal com foco de teclado tratado para uso corporativo.',
59454
59792
  icon: 'schedule',
59455
59793
  inputs: [
59456
- { name: 'metadata', type: 'MaterialTimeRangeMetadata', description: 'Configuracao do campo' },
59794
+ { name: 'metadata', type: 'MaterialTimeRangeMetadata', description: 'Configuração do campo' },
59457
59795
  { name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
59458
59796
  { name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
59459
59797
  { name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
@@ -59468,10 +59806,10 @@ const PDX_INLINE_TREE_SELECT_COMPONENT_METADATA = {
59468
59806
  selector: 'pdx-inline-tree-select',
59469
59807
  component: InlineTreeSelectComponent,
59470
59808
  friendlyName: 'Inline Tree Select',
59471
- description: 'Selecao hierarquica compacta para barras de filtro dinamico, com busca no popover e resumo em pill.',
59809
+ description: 'Seleção hierárquica compacta para barras de filtro dinâmico, com busca no popover e resumo em pill.',
59472
59810
  icon: 'account_tree',
59473
59811
  inputs: [
59474
- { name: 'metadata', type: 'MaterialTreeSelectMetadata', description: 'Configuracao do campo' },
59812
+ { name: 'metadata', type: 'MaterialTreeSelectMetadata', description: 'Configuração do campo' },
59475
59813
  { name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
59476
59814
  { name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
59477
59815
  { name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
@@ -59504,14 +59842,14 @@ const PDX_INLINE_DISTANCE_RADIUS_COMPONENT_METADATA = {
59504
59842
  selector: 'pdx-inline-distance-radius',
59505
59843
  component: InlineDistanceRadiusComponent,
59506
59844
  friendlyName: 'Inline Distance Radius',
59507
- description: 'Filtro inline de raio de distancia com visual radial, slider single/range, toggle de unidade (km/mi) e presets.',
59845
+ description: 'Filtro inline de raio de distância com visual radial, slider single/range, toggle de unidade (km/mi) e presets.',
59508
59846
  icon: 'place',
59509
59847
  inputs: [
59510
- { name: 'metadata', type: 'MaterialRangeSliderMetadata', description: 'Configuracao do campo' },
59848
+ { name: 'metadata', type: 'MaterialRangeSliderMetadata', description: 'Configuração do campo' },
59511
59849
  { name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
59512
59850
  { name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
59513
59851
  { name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
59514
- { name: 'presentationMode', type: 'boolean', default: false, description: 'Modo de apresentacao' },
59852
+ { name: 'presentationMode', type: 'boolean', default: false, description: 'Modo de apresentação' },
59515
59853
  ],
59516
59854
  tags: ['widget', 'field', 'distance', 'radius', 'filter', 'inline'],
59517
59855
  lib: '@praxisui/dynamic-fields',
@@ -59522,14 +59860,14 @@ const PDX_INLINE_PIPELINE_STATUS_COMPONENT_METADATA = {
59522
59860
  selector: 'pdx-inline-pipeline-status',
59523
59861
  component: InlinePipelineStatusComponent,
59524
59862
  friendlyName: 'Inline Pipeline Status',
59525
- description: 'Filtro inline de status/etapas com popover, barra segmentada e selecao single ou multipla totalmente configuravel por metadados.',
59863
+ description: 'Filtro inline de status/etapas com popover, barra segmentada e seleção single ou múltipla totalmente configurável por metadados.',
59526
59864
  icon: 'timeline',
59527
59865
  inputs: [
59528
- { name: 'metadata', type: 'MaterialSelectMetadata', description: 'Configuracao do campo' },
59866
+ { name: 'metadata', type: 'MaterialSelectMetadata', description: 'Configuração do campo' },
59529
59867
  { name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
59530
59868
  { name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
59531
59869
  { name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
59532
- { name: 'presentationMode', type: 'boolean', default: false, description: 'Modo de apresentacao' },
59870
+ { name: 'presentationMode', type: 'boolean', default: false, description: 'Modo de apresentação' },
59533
59871
  ],
59534
59872
  tags: ['widget', 'field', 'pipeline', 'status', 'filter', 'inline', 'multiselect'],
59535
59873
  lib: '@praxisui/dynamic-fields',
@@ -59543,7 +59881,7 @@ const PDX_INLINE_SCORE_PRIORITY_COMPONENT_METADATA = {
59543
59881
  description: 'Filtro inline de score/prioridade com faixa colorida, slider single/range, bandas configuráveis por metadados preservadas integralmente para semântica decisória, rótulos de escala com autoajuste de densidade e painel em popover não-modal.',
59544
59882
  icon: 'speed',
59545
59883
  inputs: [
59546
- { name: 'metadata', type: 'MaterialRangeSliderMetadata', description: 'Configuracao do campo' },
59884
+ { name: 'metadata', type: 'MaterialRangeSliderMetadata', description: 'Configuração do campo' },
59547
59885
  { name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
59548
59886
  { name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
59549
59887
  { name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
@@ -59594,10 +59932,10 @@ const PDX_INLINE_COLOR_LABEL_COMPONENT_METADATA = {
59594
59932
  selector: 'pdx-inline-color-label',
59595
59933
  component: InlineColorLabelComponent,
59596
59934
  friendlyName: 'Inline Color Label',
59597
- description: 'Filtro inline de cor/label com grid de cores, selecao visual e badges removiveis.',
59935
+ description: 'Filtro inline de cor/label com grid de cores, seleção visual e badges removíveis.',
59598
59936
  icon: 'palette',
59599
59937
  inputs: [
59600
- { name: 'metadata', type: 'MaterialSelectMetadata', description: 'Configuracao do campo' },
59938
+ { name: 'metadata', type: 'MaterialSelectMetadata', description: 'Configuração do campo' },
59601
59939
  { name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
59602
59940
  { name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
59603
59941
  { name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
@@ -61265,7 +61603,7 @@ const INLINE_DISTANCE_RADIUS_PREVIEW_METADATA = {
61265
61603
  distanceUnit: 'km',
61266
61604
  distanceBaseUnit: 'km',
61267
61605
  distanceAllowUnitToggle: true,
61268
- distanceSubtitle: 'Visualizacao radial com controle de raio e unidade',
61606
+ distanceSubtitle: 'Visualização radial com controle de raio e unidade',
61269
61607
  distanceRadialRings: 5,
61270
61608
  distanceAccentColor: '#3f5be6',
61271
61609
  inlineAutoSize: {
@@ -62370,10 +62708,13 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
62370
62708
  apiPath: jsonApiPath('material-file-upload/pdx-material-file-upload.json-api.md'),
62371
62709
  metadata: {
62372
62710
  accept: '.pdf,.png,.jpg',
62373
- hint: 'Current runtime is a lightweight placeholder based on native file input.',
62711
+ maxFileSize: 4194304,
62712
+ maxFiles: 1,
62713
+ imagePreview: true,
62714
+ hint: 'Select a file locally; image flows show a preview before the host persists the file.',
62374
62715
  },
62375
- snippet: `{ "name": "attachment", "controlType": "upload", "accept": ".pdf,.png,.jpg", "hint": "Current runtime is a lightweight placeholder based on native file input." }`,
62376
- snippetNote: 'This preview intentionally demonstrates the current placeholder runtime, not a full asset-management experience.',
62716
+ snippet: `{ "name": "attachment", "controlType": "upload", "accept": ".pdf,.png,.jpg", "maxFileSize": 4194304, "maxFiles": 1, "imagePreview": true, "hint": "Select a file locally; image flows show a preview before the host persists the file." }`,
62717
+ snippetNote: 'This preview demonstrates local selection, validation and image preview. Use @praxisui/files-upload for presign, progress and persisted storage.',
62377
62718
  }),
62378
62719
  createEntry({
62379
62720
  id: 'color-input',
@@ -62479,7 +62820,7 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
62479
62820
  metadataPatch: {
62480
62821
  extra: {
62481
62822
  icon: 'person',
62482
- ariaLabel: 'Avatar com icone de pessoa',
62823
+ ariaLabel: 'Avatar com ícone de pessoa',
62483
62824
  size: 'medium',
62484
62825
  fillMode: 'outline',
62485
62826
  themeColor: 'primary',
@@ -64068,7 +64409,7 @@ const DISPLAY_ACTION_AI_CAPABILITIES = {
64068
64409
  category: 'appearance',
64069
64410
  valueKind: 'enum',
64070
64411
  allowedValues: ['primary', 'secondary', 'tertiary', 'base', 'info', 'success', 'warning', 'error', 'dark', 'light', 'inverse', 'none'],
64071
- description: 'Cor semantica do avatar baseada nos tokens de tema Praxis.',
64412
+ description: 'Cor semântica do avatar baseada nos tokens de tema Praxis.',
64072
64413
  },
64073
64414
  {
64074
64415
  path: 'rounded',
@@ -64212,23 +64553,26 @@ const FILE_UPLOAD_AI_CAPABILITIES = {
64212
64553
  uploadMethod: ['POST', 'PUT', 'PATCH'],
64213
64554
  },
64214
64555
  notes: [
64215
- 'Capabilities for file upload controls.',
64556
+ 'Capabilities for file upload controls. The baseline pdx-material-file-upload runtime owns local selection, local validation and optional local image preview.',
64216
64557
  'Inherits basic capabilities from FieldMetadata (label, hint, validators).',
64558
+ 'Persistence capabilities such as uploadUrl, autoUpload, chunkSize, headers, credentials and method must be grounded by @praxisui/files-upload or a backend upload capability; they are not executed by the baseline local selector alone.',
64217
64559
  ],
64218
64560
  capabilities: [
64219
64561
  {
64220
64562
  path: 'uploadUrl',
64221
64563
  category: 'data',
64222
64564
  valueKind: 'string',
64223
- description: 'URL do endpoint para upload de arquivos.',
64565
+ description: 'URL do endpoint para upload de arquivos em fluxo persistente governado.',
64224
64566
  intentExamples: ['endpoint de upload de imagens', 'salvar arquivos em /api/docs'],
64567
+ safetyNotes: 'Requer runtime/capability de persistencia como @praxisui/files-upload; o seletor baseline nao executa upload HTTP.',
64225
64568
  },
64226
64569
  {
64227
64570
  path: 'autoUpload',
64228
64571
  category: 'behavior',
64229
64572
  valueKind: 'boolean',
64230
- description: 'Se true, inicia o upload automaticamente após a seleção do arquivo.',
64573
+ description: 'Se true, inicia o upload automaticamente após a seleção do arquivo em fluxo persistente governado.',
64231
64574
  intentExamples: ['upload automático', 'não iniciar upload imediatamente'],
64575
+ safetyNotes: 'Requer runtime/capability de persistencia; o seletor baseline apenas emite File/File[].',
64232
64576
  },
64233
64577
  {
64234
64578
  path: 'multiple',
@@ -64244,6 +64588,21 @@ const FILE_UPLOAD_AI_CAPABILITIES = {
64244
64588
  description: 'Tipos de arquivo aceitos (ex: "image/*", ".pdf").',
64245
64589
  intentExamples: ['aceitar apenas imagens', 'apenas PDF'],
64246
64590
  },
64591
+ {
64592
+ path: 'imagePreview',
64593
+ category: 'appearance',
64594
+ valueKind: 'boolean',
64595
+ description: 'Exibe preview local para imagens selecionadas antes da persistencia pelo host.',
64596
+ intentExamples: ['mostrar preview da foto escolhida', 'pré-visualizar avatar antes de salvar'],
64597
+ },
64598
+ {
64599
+ path: 'preview',
64600
+ category: 'appearance',
64601
+ valueKind: 'string',
64602
+ allowedValues: ['image'],
64603
+ description: 'Modo de preview local. Use "image" para fluxos de foto/avatar.',
64604
+ safetyNotes: 'Preview local não substitui upload, presign, crop persistido ou validação backend.',
64605
+ },
64247
64606
  {
64248
64607
  path: 'maxFileSize',
64249
64608
  category: 'validation',
@@ -64270,18 +64629,21 @@ const FILE_UPLOAD_AI_CAPABILITIES = {
64270
64629
  category: 'behavior',
64271
64630
  valueKind: 'number',
64272
64631
  description: 'Tamanho dos chunks em bytes para uploads grandes (file chunking).',
64632
+ safetyNotes: 'Aplicavel ao fluxo @praxisui/files-upload ou backend equivalente, nao ao seletor local baseline.',
64273
64633
  },
64274
64634
  {
64275
64635
  path: 'withCredentials',
64276
64636
  category: 'data',
64277
64637
  valueKind: 'boolean',
64278
64638
  description: 'Envia cookies e headers de autorização com a requisição de upload.',
64639
+ safetyNotes: 'Aplicavel ao fluxo @praxisui/files-upload ou backend equivalente, nao ao seletor local baseline.',
64279
64640
  },
64280
64641
  {
64281
64642
  path: 'headers',
64282
64643
  category: 'data',
64283
64644
  valueKind: 'object',
64284
64645
  description: 'Cabeçalhos HTTP personalizados para a requisição de upload.',
64646
+ safetyNotes: 'Aplicavel ao fluxo @praxisui/files-upload ou backend equivalente, nao ao seletor local baseline.',
64285
64647
  },
64286
64648
  {
64287
64649
  path: 'method',
@@ -64289,6 +64651,7 @@ const FILE_UPLOAD_AI_CAPABILITIES = {
64289
64651
  valueKind: 'enum',
64290
64652
  allowedValues: ['POST', 'PUT', 'PATCH'],
64291
64653
  description: 'Método HTTP para a requisição de upload.',
64654
+ safetyNotes: 'Aplicavel ao fluxo @praxisui/files-upload ou backend equivalente, nao ao seletor local baseline.',
64292
64655
  },
64293
64656
  {
64294
64657
  path: 'saveField',
@@ -66169,7 +66532,7 @@ function mapValuePresentation(value) {
66169
66532
  const typeToken = String(value.type ?? '').trim();
66170
66533
  if (!typeToken || !VALUE_PRESENTATION_TYPES.has(typeToken)) {
66171
66534
  if (typeToken) {
66172
- logger.warn(`[JsonSchemaMapper] Ignorando x-ui.valuePresentation.type invalido: ${typeToken}`);
66535
+ logger.warn(`[JsonSchemaMapper] Ignorando x-ui.valuePresentation.type inválido: ${typeToken}`);
66173
66536
  }
66174
66537
  return undefined;
66175
66538
  }
@@ -66182,7 +66545,7 @@ function mapValuePresentation(value) {
66182
66545
  presentation.style = styleToken;
66183
66546
  }
66184
66547
  else {
66185
- logger.warn(`[JsonSchemaMapper] Ignorando x-ui.valuePresentation.style invalido: ${styleToken}`);
66548
+ logger.warn(`[JsonSchemaMapper] Ignorando x-ui.valuePresentation.style inválido: ${styleToken}`);
66186
66549
  }
66187
66550
  }
66188
66551
  const formatToken = String(value.format ?? '').trim();