@praxisui/dynamic-fields 9.0.0-beta.16 → 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.
- package/README.md +2 -0
- package/ai/component-registry.json +112 -112
- package/docs/dynamic-fields-field-catalog.md +1 -2
- package/fesm2022/praxisui-dynamic-fields.mjs +442 -70
- package/package.json +3 -3
- package/src/lib/components/material-file-upload/pdx-material-file-upload.json-api.md +58 -27
- package/types/praxisui-dynamic-fields.d.ts +51 -5
|
@@ -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': '
|
|
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}
|
|
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': '
|
|
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
|
|
2984
|
-
message: meta.confirmDialog.message || this.tDynamicFields('button.confirmMessage', 'Deseja continuar com esta
|
|
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 = '--
|
|
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
|
}
|
|
@@ -9064,7 +9086,7 @@ function resolveFieldPresentation(field, value, options) {
|
|
|
9064
9086
|
const config = buildValuePresentationConfig(field, value);
|
|
9065
9087
|
if (!config)
|
|
9066
9088
|
return null;
|
|
9067
|
-
const effectiveLocalization = mergeLocalization(
|
|
9089
|
+
const effectiveLocalization = mergeLocalization(mergeLocalization(buildLegacyFieldLocalization(field), field?.localization), options?.localization);
|
|
9068
9090
|
const fallbackCurrencyCode = config.type === 'currency' && !config.currency?.code
|
|
9069
9091
|
? inferCurrencyCodeFromLocale(effectiveLocalization?.locale ||
|
|
9070
9092
|
options?.surfaceLocale ||
|
|
@@ -9072,7 +9094,7 @@ function resolveFieldPresentation(field, value, options) {
|
|
|
9072
9094
|
undefined)
|
|
9073
9095
|
: undefined;
|
|
9074
9096
|
return resolveValuePresentation(config, {
|
|
9075
|
-
valueLocale:
|
|
9097
|
+
valueLocale: effectiveLocalization?.locale,
|
|
9076
9098
|
localization: fallbackCurrencyCode
|
|
9077
9099
|
? mergeLocalization(effectiveLocalization, {
|
|
9078
9100
|
...(effectiveLocalization?.locale ? { locale: effectiveLocalization.locale } : {}),
|
|
@@ -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', '
|
|
9381
|
+
return ['Sim', 'Não'];
|
|
9360
9382
|
}
|
|
9361
9383
|
if (normalized.startsWith('es')) {
|
|
9362
9384
|
return ['Si', 'No'];
|
|
@@ -9729,7 +9751,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
9729
9751
|
* project additional chrome around the field.
|
|
9730
9752
|
*/
|
|
9731
9753
|
class FieldShellComponent {
|
|
9732
|
-
|
|
9754
|
+
angularLocale = inject(LOCALE_ID, { optional: true }) ?? 'en-US';
|
|
9755
|
+
i18n = inject(PraxisI18nService, { optional: true });
|
|
9733
9756
|
cdr = inject(ChangeDetectorRef);
|
|
9734
9757
|
optionDisplayResolver = inject(OptionDisplayResolverService);
|
|
9735
9758
|
crudService = inject(GenericCrudService, {
|
|
@@ -9898,7 +9921,7 @@ class FieldShellComponent {
|
|
|
9898
9921
|
const formatted = typeof fn === 'function'
|
|
9899
9922
|
? fn(raw)
|
|
9900
9923
|
: formatDisplayValue(this.field, raw, {
|
|
9901
|
-
appLocale: this.
|
|
9924
|
+
appLocale: this.resolvePresentationAppLocale(),
|
|
9902
9925
|
surfaceLocale: this.field?.localization?.locale ?? this.field?.locale ?? undefined,
|
|
9903
9926
|
localization: this.field?.localization ?? undefined,
|
|
9904
9927
|
});
|
|
@@ -9906,7 +9929,7 @@ class FieldShellComponent {
|
|
|
9906
9929
|
}
|
|
9907
9930
|
catch {
|
|
9908
9931
|
return formatDisplayValue(this.field, raw, {
|
|
9909
|
-
appLocale: this.
|
|
9932
|
+
appLocale: this.resolvePresentationAppLocale(),
|
|
9910
9933
|
surfaceLocale: this.field?.localization?.locale ?? this.field?.locale ?? undefined,
|
|
9911
9934
|
localization: this.field?.localization ?? undefined,
|
|
9912
9935
|
});
|
|
@@ -9924,14 +9947,14 @@ class FieldShellComponent {
|
|
|
9924
9947
|
: null;
|
|
9925
9948
|
const val = formatted == null ? raw : formatted;
|
|
9926
9949
|
return formatDisplayHtml(this.field, val, {
|
|
9927
|
-
appLocale: this.
|
|
9950
|
+
appLocale: this.resolvePresentationAppLocale(),
|
|
9928
9951
|
surfaceLocale: this.field?.localization?.locale ?? this.field?.locale ?? undefined,
|
|
9929
9952
|
localization: this.field?.localization ?? undefined,
|
|
9930
9953
|
});
|
|
9931
9954
|
}
|
|
9932
9955
|
catch {
|
|
9933
9956
|
return formatDisplayHtml(this.field, raw, {
|
|
9934
|
-
appLocale: this.
|
|
9957
|
+
appLocale: this.resolvePresentationAppLocale(),
|
|
9935
9958
|
surfaceLocale: this.field?.localization?.locale ?? this.field?.locale ?? undefined,
|
|
9936
9959
|
localization: this.field?.localization ?? undefined,
|
|
9937
9960
|
});
|
|
@@ -10029,6 +10052,14 @@ class FieldShellComponent {
|
|
|
10029
10052
|
const value = String(position ?? '').trim().toLowerCase();
|
|
10030
10053
|
return ['end', 'right', 'after', 'suffix', 'trailing'].includes(value);
|
|
10031
10054
|
}
|
|
10055
|
+
resolvePresentationAppLocale() {
|
|
10056
|
+
const praxisLocale = this.i18n?.getLocale()?.trim();
|
|
10057
|
+
if (praxisLocale) {
|
|
10058
|
+
return praxisLocale;
|
|
10059
|
+
}
|
|
10060
|
+
const angularLocale = this.angularLocale?.trim();
|
|
10061
|
+
return angularLocale || 'en-US';
|
|
10062
|
+
}
|
|
10032
10063
|
refreshPresentationDisplayValue() {
|
|
10033
10064
|
this.resolveSub?.unsubscribe();
|
|
10034
10065
|
this.resolveSub = undefined;
|
|
@@ -13108,7 +13139,7 @@ class InlineEntityLookupComponent extends MaterialAsyncSelectComponent {
|
|
|
13108
13139
|
}
|
|
13109
13140
|
selectionLimitReachedText(maxSelections) {
|
|
13110
13141
|
const max = Number(maxSelections ?? 0);
|
|
13111
|
-
const template = this.tDynamicFields('entityLookup.selectionLimitReached', 'Limite de {max}
|
|
13142
|
+
const template = this.tDynamicFields('entityLookup.selectionLimitReached', 'Limite de {max} seleções atingido.', { max: Number.isFinite(max) && max > 0 ? max : 0 });
|
|
13112
13143
|
return template.replace('{max}', String(Number.isFinite(max) && max > 0 ? max : 0));
|
|
13113
13144
|
}
|
|
13114
13145
|
onDetailAction(event, item) {
|
|
@@ -13278,7 +13309,7 @@ class InlineEntityLookupComponent extends MaterialAsyncSelectComponent {
|
|
|
13278
13309
|
}
|
|
13279
13310
|
resetOptionText() {
|
|
13280
13311
|
const custom = String(this.metadata()?.resetLabel || '').trim();
|
|
13281
|
-
return custom || 'Limpar
|
|
13312
|
+
return custom || 'Limpar seleção';
|
|
13282
13313
|
}
|
|
13283
13314
|
showQuickClear() {
|
|
13284
13315
|
const clearCfg = this.currentMetadata().clearButton;
|
|
@@ -13382,7 +13413,7 @@ class InlineEntityLookupComponent extends MaterialAsyncSelectComponent {
|
|
|
13382
13413
|
this.retry();
|
|
13383
13414
|
}
|
|
13384
13415
|
errorStateLabel() {
|
|
13385
|
-
return this.error() || this.tDynamicFields('select.loadOptionsError', 'Erro ao carregar
|
|
13416
|
+
return this.error() || this.tDynamicFields('select.loadOptionsError', 'Erro ao carregar opções.');
|
|
13386
13417
|
}
|
|
13387
13418
|
dependencyStateLabel() {
|
|
13388
13419
|
const dependencyLabels = this.missingDependencyLabels();
|
|
@@ -13453,7 +13484,7 @@ class InlineEntityLookupComponent extends MaterialAsyncSelectComponent {
|
|
|
13453
13484
|
return this.tDynamicFields('entityLookup.sort.label', 'Ordenar por');
|
|
13454
13485
|
}
|
|
13455
13486
|
sortDefaultOptionText() {
|
|
13456
|
-
return this.tDynamicFields('entityLookup.sort.default', '
|
|
13487
|
+
return this.tDynamicFields('entityLookup.sort.default', 'Padrão');
|
|
13457
13488
|
}
|
|
13458
13489
|
clearAllFiltersText() {
|
|
13459
13490
|
return this.tDynamicFields('entityLookup.filter.clearAll', 'Limpar filtros');
|
|
@@ -17626,11 +17657,11 @@ function createInlinePhoneComponentMetadata(locale = 'en-US') {
|
|
|
17626
17657
|
}
|
|
17627
17658
|
|
|
17628
17659
|
/**
|
|
17629
|
-
*
|
|
17660
|
+
* Baseline file upload field.
|
|
17630
17661
|
*
|
|
17631
|
-
*
|
|
17632
|
-
*
|
|
17633
|
-
*
|
|
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.
|
|
17634
17665
|
*/
|
|
17635
17666
|
class MaterialFileUploadComponent extends SimpleBaseInputComponent {
|
|
17636
17667
|
// Praxis Field States
|
|
@@ -17638,13 +17669,55 @@ class MaterialFileUploadComponent extends SimpleBaseInputComponent {
|
|
|
17638
17669
|
disabledMode = false;
|
|
17639
17670
|
visible = true;
|
|
17640
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 */ []));
|
|
17641
17694
|
/**
|
|
17642
17695
|
* Updates the control value when the user selects a file.
|
|
17643
17696
|
*/
|
|
17644
17697
|
onFileSelected(event) {
|
|
17645
17698
|
const input = event.target;
|
|
17646
|
-
const
|
|
17647
|
-
|
|
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();
|
|
17648
17721
|
}
|
|
17649
17722
|
/**
|
|
17650
17723
|
* Exposes metadata setter for integration with dynamic form loader.
|
|
@@ -17660,8 +17733,71 @@ class MaterialFileUploadComponent extends SimpleBaseInputComponent {
|
|
|
17660
17733
|
allowsMultiple() {
|
|
17661
17734
|
return this.metadataRecord()?.['multiple'] === true;
|
|
17662
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
|
+
}
|
|
17663
17745
|
isInteractionDisabled() {
|
|
17664
|
-
return this.
|
|
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();
|
|
17665
17801
|
}
|
|
17666
17802
|
/**
|
|
17667
17803
|
* CSS classes specific to the file upload wrapper.
|
|
@@ -17669,12 +17805,168 @@ class MaterialFileUploadComponent extends SimpleBaseInputComponent {
|
|
|
17669
17805
|
getSpecificCssClasses() {
|
|
17670
17806
|
return ['pdx-material-file-upload'];
|
|
17671
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
|
+
}
|
|
17672
17816
|
metadataRecord() {
|
|
17673
17817
|
const metadata = this.metadata();
|
|
17674
17818
|
return metadata && typeof metadata === 'object'
|
|
17675
17819
|
? metadata
|
|
17676
17820
|
: null;
|
|
17677
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
|
+
}
|
|
17678
17970
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: MaterialFileUploadComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
17679
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: [
|
|
17680
17972
|
{
|
|
@@ -17697,24 +17989,51 @@ class MaterialFileUploadComponent extends SimpleBaseInputComponent {
|
|
|
17697
17989
|
[attr.accept]="fileAccept()"
|
|
17698
17990
|
[attr.multiple]="allowsMultiple() ? '' : null"
|
|
17699
17991
|
[disabled]="isInteractionDisabled()"
|
|
17700
|
-
[attr.aria-disabled]="
|
|
17992
|
+
[attr.aria-disabled]="isInteractionDisabled() ? 'true' : null"
|
|
17993
|
+
[attr.aria-describedby]="fileInputDescribedBy()"
|
|
17994
|
+
[attr.aria-invalid]="hasValidationError() ? 'true' : null"
|
|
17995
|
+
[attr.aria-label]="inputAriaLabel()"
|
|
17701
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
|
+
}
|
|
17702
18025
|
@if (hasValidationError()) {
|
|
17703
|
-
<div class="error">{{ errorMessage() }}</div>
|
|
18026
|
+
<div class="error" [attr.id]="errorId()">{{ errorMessage() }}</div>
|
|
17704
18027
|
}
|
|
17705
18028
|
@if (metadata()?.hint && !hasValidationError()) {
|
|
17706
|
-
<div class="hint">{{ metadata()!.hint }}</div>
|
|
18029
|
+
<div class="hint" [attr.id]="hintId()">{{ metadata()!.hint }}</div>
|
|
17707
18030
|
}
|
|
17708
18031
|
</div>
|
|
17709
|
-
`, 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"] }] });
|
|
17710
18033
|
}
|
|
17711
18034
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: MaterialFileUploadComponent, decorators: [{
|
|
17712
18035
|
type: Component,
|
|
17713
|
-
args: [{
|
|
17714
|
-
selector: 'pdx-material-file-upload',
|
|
17715
|
-
standalone: true,
|
|
17716
|
-
imports: [ReactiveFormsModule, MatTooltipModule],
|
|
17717
|
-
template: `
|
|
18036
|
+
args: [{ selector: 'pdx-material-file-upload', standalone: true, imports: [ReactiveFormsModule, MatTooltipModule], template: `
|
|
17718
18037
|
<div
|
|
17719
18038
|
class="pdx-file-upload"
|
|
17720
18039
|
[matTooltip]="tooltipEnabled() ? errorMessage() : null"
|
|
@@ -17729,24 +18048,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
17729
18048
|
[attr.accept]="fileAccept()"
|
|
17730
18049
|
[attr.multiple]="allowsMultiple() ? '' : null"
|
|
17731
18050
|
[disabled]="isInteractionDisabled()"
|
|
17732
|
-
[attr.aria-disabled]="
|
|
18051
|
+
[attr.aria-disabled]="isInteractionDisabled() ? 'true' : null"
|
|
18052
|
+
[attr.aria-describedby]="fileInputDescribedBy()"
|
|
18053
|
+
[attr.aria-invalid]="hasValidationError() ? 'true' : null"
|
|
18054
|
+
[attr.aria-label]="inputAriaLabel()"
|
|
17733
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
|
+
}
|
|
17734
18084
|
@if (hasValidationError()) {
|
|
17735
|
-
<div class="error">{{ errorMessage() }}</div>
|
|
18085
|
+
<div class="error" [attr.id]="errorId()">{{ errorMessage() }}</div>
|
|
17736
18086
|
}
|
|
17737
18087
|
@if (metadata()?.hint && !hasValidationError()) {
|
|
17738
|
-
<div class="hint">{{ metadata()!.hint }}</div>
|
|
18088
|
+
<div class="hint" [attr.id]="hintId()">{{ metadata()!.hint }}</div>
|
|
17739
18089
|
}
|
|
17740
18090
|
</div>
|
|
17741
|
-
`,
|
|
17742
|
-
providers: [
|
|
18091
|
+
`, providers: [
|
|
17743
18092
|
{
|
|
17744
18093
|
provide: NG_VALUE_ACCESSOR,
|
|
17745
18094
|
useExisting: forwardRef(() => MaterialFileUploadComponent),
|
|
17746
18095
|
multi: true,
|
|
17747
18096
|
},
|
|
17748
|
-
],
|
|
17749
|
-
host: {
|
|
18097
|
+
], host: {
|
|
17750
18098
|
'[class]': 'componentCssClasses()',
|
|
17751
18099
|
'[class.praxis-disabled]': 'disabledMode',
|
|
17752
18100
|
'[style.display]': 'visible ? null : "none"',
|
|
@@ -17754,8 +18102,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
17754
18102
|
'[attr.data-field-type]': '"file-upload"',
|
|
17755
18103
|
'[attr.data-field-name]': 'metadata()?.name',
|
|
17756
18104
|
'[attr.data-component-id]': 'componentId()',
|
|
17757
|
-
},
|
|
17758
|
-
}]
|
|
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"] }]
|
|
17759
18106
|
}], propDecorators: { readonlyMode: [{
|
|
17760
18107
|
type: Input
|
|
17761
18108
|
}], disabledMode: [{
|
|
@@ -43610,7 +43957,7 @@ class InlineDistanceRadiusComponent extends InlineRangeSliderComponent {
|
|
|
43610
43957
|
const subtitle = String(md.distanceSubtitle ?? md.inlinePanelSubtitle ?? md.inlineSubtitle ?? md.hint ?? '').trim();
|
|
43611
43958
|
if (subtitle.length)
|
|
43612
43959
|
return subtitle;
|
|
43613
|
-
return this.distanceText('inlineDistance.panelSubtitle', '
|
|
43960
|
+
return this.distanceText('inlineDistance.panelSubtitle', 'Visualização radial com controle de raio e unidade');
|
|
43614
43961
|
}
|
|
43615
43962
|
unitToggleAriaLabel() {
|
|
43616
43963
|
return this.distanceText('inlineDistance.unitToggleAriaLabel', 'Unidade de distancia atual: {{unit}}', {
|
|
@@ -59405,7 +59752,7 @@ const PDX_INLINE_DATE_RANGE_COMPONENT_METADATA = {
|
|
|
59405
59752
|
selector: 'pdx-inline-date-range',
|
|
59406
59753
|
component: InlineDateRangeComponent,
|
|
59407
59754
|
friendlyName: 'Inline Date Range',
|
|
59408
|
-
description: 'Intervalo de datas compacto para barras de filtro
|
|
59755
|
+
description: 'Intervalo de datas compacto para barras de filtro dinâmico, com autosize e presets rápidos no overlay do calendário.',
|
|
59409
59756
|
icon: 'date_range',
|
|
59410
59757
|
inputs: [
|
|
59411
59758
|
{ name: 'metadata', type: 'MaterialDateRangeMetadata', description: 'Configuração do campo' },
|
|
@@ -59423,10 +59770,10 @@ const PDX_INLINE_TIME_COMPONENT_METADATA = {
|
|
|
59423
59770
|
selector: 'pdx-inline-time',
|
|
59424
59771
|
component: InlineTimeComponent,
|
|
59425
59772
|
friendlyName: 'Inline Time',
|
|
59426
|
-
description: 'Timepicker compacto para barras de filtro
|
|
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.',
|
|
59427
59774
|
icon: 'schedule',
|
|
59428
59775
|
inputs: [
|
|
59429
|
-
{ name: 'metadata', type: 'MaterialTimepickerMetadata', description: '
|
|
59776
|
+
{ name: 'metadata', type: 'MaterialTimepickerMetadata', description: 'Configuração do campo' },
|
|
59430
59777
|
{ name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
|
|
59431
59778
|
{ name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
|
|
59432
59779
|
{ name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
|
|
@@ -59444,7 +59791,7 @@ const PDX_INLINE_TIME_RANGE_COMPONENT_METADATA = {
|
|
|
59444
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.',
|
|
59445
59792
|
icon: 'schedule',
|
|
59446
59793
|
inputs: [
|
|
59447
|
-
{ name: 'metadata', type: 'MaterialTimeRangeMetadata', description: '
|
|
59794
|
+
{ name: 'metadata', type: 'MaterialTimeRangeMetadata', description: 'Configuração do campo' },
|
|
59448
59795
|
{ name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
|
|
59449
59796
|
{ name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
|
|
59450
59797
|
{ name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
|
|
@@ -59459,10 +59806,10 @@ const PDX_INLINE_TREE_SELECT_COMPONENT_METADATA = {
|
|
|
59459
59806
|
selector: 'pdx-inline-tree-select',
|
|
59460
59807
|
component: InlineTreeSelectComponent,
|
|
59461
59808
|
friendlyName: 'Inline Tree Select',
|
|
59462
|
-
description: '
|
|
59809
|
+
description: 'Seleção hierárquica compacta para barras de filtro dinâmico, com busca no popover e resumo em pill.',
|
|
59463
59810
|
icon: 'account_tree',
|
|
59464
59811
|
inputs: [
|
|
59465
|
-
{ name: 'metadata', type: 'MaterialTreeSelectMetadata', description: '
|
|
59812
|
+
{ name: 'metadata', type: 'MaterialTreeSelectMetadata', description: 'Configuração do campo' },
|
|
59466
59813
|
{ name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
|
|
59467
59814
|
{ name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
|
|
59468
59815
|
{ name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
|
|
@@ -59495,14 +59842,14 @@ const PDX_INLINE_DISTANCE_RADIUS_COMPONENT_METADATA = {
|
|
|
59495
59842
|
selector: 'pdx-inline-distance-radius',
|
|
59496
59843
|
component: InlineDistanceRadiusComponent,
|
|
59497
59844
|
friendlyName: 'Inline Distance Radius',
|
|
59498
|
-
description: 'Filtro inline de raio de
|
|
59845
|
+
description: 'Filtro inline de raio de distância com visual radial, slider single/range, toggle de unidade (km/mi) e presets.',
|
|
59499
59846
|
icon: 'place',
|
|
59500
59847
|
inputs: [
|
|
59501
|
-
{ name: 'metadata', type: 'MaterialRangeSliderMetadata', description: '
|
|
59848
|
+
{ name: 'metadata', type: 'MaterialRangeSliderMetadata', description: 'Configuração do campo' },
|
|
59502
59849
|
{ name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
|
|
59503
59850
|
{ name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
|
|
59504
59851
|
{ name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
|
|
59505
|
-
{ name: 'presentationMode', type: 'boolean', default: false, description: 'Modo de
|
|
59852
|
+
{ name: 'presentationMode', type: 'boolean', default: false, description: 'Modo de apresentação' },
|
|
59506
59853
|
],
|
|
59507
59854
|
tags: ['widget', 'field', 'distance', 'radius', 'filter', 'inline'],
|
|
59508
59855
|
lib: '@praxisui/dynamic-fields',
|
|
@@ -59513,14 +59860,14 @@ const PDX_INLINE_PIPELINE_STATUS_COMPONENT_METADATA = {
|
|
|
59513
59860
|
selector: 'pdx-inline-pipeline-status',
|
|
59514
59861
|
component: InlinePipelineStatusComponent,
|
|
59515
59862
|
friendlyName: 'Inline Pipeline Status',
|
|
59516
|
-
description: 'Filtro inline de status/etapas com popover, barra segmentada e
|
|
59863
|
+
description: 'Filtro inline de status/etapas com popover, barra segmentada e seleção single ou múltipla totalmente configurável por metadados.',
|
|
59517
59864
|
icon: 'timeline',
|
|
59518
59865
|
inputs: [
|
|
59519
|
-
{ name: 'metadata', type: 'MaterialSelectMetadata', description: '
|
|
59866
|
+
{ name: 'metadata', type: 'MaterialSelectMetadata', description: 'Configuração do campo' },
|
|
59520
59867
|
{ name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
|
|
59521
59868
|
{ name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
|
|
59522
59869
|
{ name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
|
|
59523
|
-
{ name: 'presentationMode', type: 'boolean', default: false, description: 'Modo de
|
|
59870
|
+
{ name: 'presentationMode', type: 'boolean', default: false, description: 'Modo de apresentação' },
|
|
59524
59871
|
],
|
|
59525
59872
|
tags: ['widget', 'field', 'pipeline', 'status', 'filter', 'inline', 'multiselect'],
|
|
59526
59873
|
lib: '@praxisui/dynamic-fields',
|
|
@@ -59534,7 +59881,7 @@ const PDX_INLINE_SCORE_PRIORITY_COMPONENT_METADATA = {
|
|
|
59534
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.',
|
|
59535
59882
|
icon: 'speed',
|
|
59536
59883
|
inputs: [
|
|
59537
|
-
{ name: 'metadata', type: 'MaterialRangeSliderMetadata', description: '
|
|
59884
|
+
{ name: 'metadata', type: 'MaterialRangeSliderMetadata', description: 'Configuração do campo' },
|
|
59538
59885
|
{ name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
|
|
59539
59886
|
{ name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
|
|
59540
59887
|
{ name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
|
|
@@ -59585,10 +59932,10 @@ const PDX_INLINE_COLOR_LABEL_COMPONENT_METADATA = {
|
|
|
59585
59932
|
selector: 'pdx-inline-color-label',
|
|
59586
59933
|
component: InlineColorLabelComponent,
|
|
59587
59934
|
friendlyName: 'Inline Color Label',
|
|
59588
|
-
description: 'Filtro inline de cor/label com grid de cores,
|
|
59935
|
+
description: 'Filtro inline de cor/label com grid de cores, seleção visual e badges removíveis.',
|
|
59589
59936
|
icon: 'palette',
|
|
59590
59937
|
inputs: [
|
|
59591
|
-
{ name: 'metadata', type: 'MaterialSelectMetadata', description: '
|
|
59938
|
+
{ name: 'metadata', type: 'MaterialSelectMetadata', description: 'Configuração do campo' },
|
|
59592
59939
|
{ name: 'readonlyMode', type: 'boolean', default: false, description: 'Define modo somente leitura' },
|
|
59593
59940
|
{ name: 'disabledMode', type: 'boolean', default: false, description: 'Desabilita o campo' },
|
|
59594
59941
|
{ name: 'visible', type: 'boolean', default: true, description: 'Controla a visibilidade' },
|
|
@@ -61256,7 +61603,7 @@ const INLINE_DISTANCE_RADIUS_PREVIEW_METADATA = {
|
|
|
61256
61603
|
distanceUnit: 'km',
|
|
61257
61604
|
distanceBaseUnit: 'km',
|
|
61258
61605
|
distanceAllowUnitToggle: true,
|
|
61259
|
-
distanceSubtitle: '
|
|
61606
|
+
distanceSubtitle: 'Visualização radial com controle de raio e unidade',
|
|
61260
61607
|
distanceRadialRings: 5,
|
|
61261
61608
|
distanceAccentColor: '#3f5be6',
|
|
61262
61609
|
inlineAutoSize: {
|
|
@@ -62361,10 +62708,13 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
|
|
|
62361
62708
|
apiPath: jsonApiPath('material-file-upload/pdx-material-file-upload.json-api.md'),
|
|
62362
62709
|
metadata: {
|
|
62363
62710
|
accept: '.pdf,.png,.jpg',
|
|
62364
|
-
|
|
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.',
|
|
62365
62715
|
},
|
|
62366
|
-
snippet: `{ "name": "attachment", "controlType": "upload", "accept": ".pdf,.png,.jpg", "hint": "
|
|
62367
|
-
snippetNote: 'This preview
|
|
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.',
|
|
62368
62718
|
}),
|
|
62369
62719
|
createEntry({
|
|
62370
62720
|
id: 'color-input',
|
|
@@ -62470,7 +62820,7 @@ const DYNAMIC_FIELDS_PLAYGROUND_CATALOG = [
|
|
|
62470
62820
|
metadataPatch: {
|
|
62471
62821
|
extra: {
|
|
62472
62822
|
icon: 'person',
|
|
62473
|
-
ariaLabel: 'Avatar com
|
|
62823
|
+
ariaLabel: 'Avatar com ícone de pessoa',
|
|
62474
62824
|
size: 'medium',
|
|
62475
62825
|
fillMode: 'outline',
|
|
62476
62826
|
themeColor: 'primary',
|
|
@@ -64059,7 +64409,7 @@ const DISPLAY_ACTION_AI_CAPABILITIES = {
|
|
|
64059
64409
|
category: 'appearance',
|
|
64060
64410
|
valueKind: 'enum',
|
|
64061
64411
|
allowedValues: ['primary', 'secondary', 'tertiary', 'base', 'info', 'success', 'warning', 'error', 'dark', 'light', 'inverse', 'none'],
|
|
64062
|
-
description: 'Cor
|
|
64412
|
+
description: 'Cor semântica do avatar baseada nos tokens de tema Praxis.',
|
|
64063
64413
|
},
|
|
64064
64414
|
{
|
|
64065
64415
|
path: 'rounded',
|
|
@@ -64203,23 +64553,26 @@ const FILE_UPLOAD_AI_CAPABILITIES = {
|
|
|
64203
64553
|
uploadMethod: ['POST', 'PUT', 'PATCH'],
|
|
64204
64554
|
},
|
|
64205
64555
|
notes: [
|
|
64206
|
-
'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.',
|
|
64207
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.',
|
|
64208
64559
|
],
|
|
64209
64560
|
capabilities: [
|
|
64210
64561
|
{
|
|
64211
64562
|
path: 'uploadUrl',
|
|
64212
64563
|
category: 'data',
|
|
64213
64564
|
valueKind: 'string',
|
|
64214
|
-
description: 'URL do endpoint para upload de arquivos.',
|
|
64565
|
+
description: 'URL do endpoint para upload de arquivos em fluxo persistente governado.',
|
|
64215
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.',
|
|
64216
64568
|
},
|
|
64217
64569
|
{
|
|
64218
64570
|
path: 'autoUpload',
|
|
64219
64571
|
category: 'behavior',
|
|
64220
64572
|
valueKind: 'boolean',
|
|
64221
|
-
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.',
|
|
64222
64574
|
intentExamples: ['upload automático', 'não iniciar upload imediatamente'],
|
|
64575
|
+
safetyNotes: 'Requer runtime/capability de persistencia; o seletor baseline apenas emite File/File[].',
|
|
64223
64576
|
},
|
|
64224
64577
|
{
|
|
64225
64578
|
path: 'multiple',
|
|
@@ -64235,6 +64588,21 @@ const FILE_UPLOAD_AI_CAPABILITIES = {
|
|
|
64235
64588
|
description: 'Tipos de arquivo aceitos (ex: "image/*", ".pdf").',
|
|
64236
64589
|
intentExamples: ['aceitar apenas imagens', 'apenas PDF'],
|
|
64237
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
|
+
},
|
|
64238
64606
|
{
|
|
64239
64607
|
path: 'maxFileSize',
|
|
64240
64608
|
category: 'validation',
|
|
@@ -64261,18 +64629,21 @@ const FILE_UPLOAD_AI_CAPABILITIES = {
|
|
|
64261
64629
|
category: 'behavior',
|
|
64262
64630
|
valueKind: 'number',
|
|
64263
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.',
|
|
64264
64633
|
},
|
|
64265
64634
|
{
|
|
64266
64635
|
path: 'withCredentials',
|
|
64267
64636
|
category: 'data',
|
|
64268
64637
|
valueKind: 'boolean',
|
|
64269
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.',
|
|
64270
64640
|
},
|
|
64271
64641
|
{
|
|
64272
64642
|
path: 'headers',
|
|
64273
64643
|
category: 'data',
|
|
64274
64644
|
valueKind: 'object',
|
|
64275
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.',
|
|
64276
64647
|
},
|
|
64277
64648
|
{
|
|
64278
64649
|
path: 'method',
|
|
@@ -64280,6 +64651,7 @@ const FILE_UPLOAD_AI_CAPABILITIES = {
|
|
|
64280
64651
|
valueKind: 'enum',
|
|
64281
64652
|
allowedValues: ['POST', 'PUT', 'PATCH'],
|
|
64282
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.',
|
|
64283
64655
|
},
|
|
64284
64656
|
{
|
|
64285
64657
|
path: 'saveField',
|
|
@@ -66160,7 +66532,7 @@ function mapValuePresentation(value) {
|
|
|
66160
66532
|
const typeToken = String(value.type ?? '').trim();
|
|
66161
66533
|
if (!typeToken || !VALUE_PRESENTATION_TYPES.has(typeToken)) {
|
|
66162
66534
|
if (typeToken) {
|
|
66163
|
-
logger.warn(`[JsonSchemaMapper] Ignorando x-ui.valuePresentation.type
|
|
66535
|
+
logger.warn(`[JsonSchemaMapper] Ignorando x-ui.valuePresentation.type inválido: ${typeToken}`);
|
|
66164
66536
|
}
|
|
66165
66537
|
return undefined;
|
|
66166
66538
|
}
|
|
@@ -66173,7 +66545,7 @@ function mapValuePresentation(value) {
|
|
|
66173
66545
|
presentation.style = styleToken;
|
|
66174
66546
|
}
|
|
66175
66547
|
else {
|
|
66176
|
-
logger.warn(`[JsonSchemaMapper] Ignorando x-ui.valuePresentation.style
|
|
66548
|
+
logger.warn(`[JsonSchemaMapper] Ignorando x-ui.valuePresentation.style inválido: ${styleToken}`);
|
|
66177
66549
|
}
|
|
66178
66550
|
}
|
|
66179
66551
|
const formatToken = String(value.format ?? '').trim();
|