valtech-components 2.0.941 → 2.0.942

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.
@@ -54,7 +54,7 @@ import 'prismjs/components/prism-json';
54
54
  * Current version of valtech-components.
55
55
  * This is automatically updated during the publish process.
56
56
  */
57
- const VERSION = '2.0.941';
57
+ const VERSION = '2.0.942';
58
58
 
59
59
  // Control de estado de refresco (singleton a nivel de módulo)
60
60
  let isRefreshing = false;
@@ -10006,6 +10006,7 @@ var InputType;
10006
10006
  InputType[InputType["CURRENCY"] = 21] = "CURRENCY";
10007
10007
  InputType[InputType["CHECKBOX_RADIO"] = 22] = "CHECKBOX_RADIO";
10008
10008
  InputType[InputType["HANDLE"] = 23] = "HANDLE";
10009
+ InputType[InputType["ATTACHMENT"] = 24] = "ATTACHMENT";
10009
10010
  })(InputType || (InputType = {}));
10010
10011
  /**
10011
10012
  * Possible action types for a toolbar.
@@ -28986,7 +28987,7 @@ class AttachmentUploaderComponent {
28986
28987
  this.props = input({});
28987
28988
  this.attachmentsChange = output();
28988
28989
  this.i18n = inject(I18nService);
28989
- this.feedbackService = inject(FeedbackService);
28990
+ this.feedbackService = inject(FeedbackService, { optional: true });
28990
28991
  this.attachments = signal([]);
28991
28992
  this.maxFiles = computed(() => this.props().maxFiles ?? 5);
28992
28993
  this.accept = computed(() => this.props().accept ?? 'image/jpeg,image/png,image/webp,application/pdf');
@@ -29034,7 +29035,10 @@ class AttachmentUploaderComponent {
29034
29035
  try {
29035
29036
  const shouldCompress = this.props().compressImages !== false && file.type.startsWith('image/');
29036
29037
  const fileToUpload = shouldCompress ? await this.compressImage(file) : file;
29037
- const url = await this.feedbackService.uploadAttachment(fileToUpload);
29038
+ const uploadFn = this.props().uploadFn ?? this.feedbackService?.uploadAttachment.bind(this.feedbackService);
29039
+ if (!uploadFn)
29040
+ throw new Error('No upload function configured');
29041
+ const url = await uploadFn(fileToUpload);
29038
29042
  this.attachments.update(list => list.map(a => (a.id === id ? { ...a, status: 'ready', url } : a)));
29039
29043
  }
29040
29044
  catch {
@@ -29963,6 +29967,7 @@ class FormComponent {
29963
29967
  this.onInvalid = new EventEmitter();
29964
29968
  this.onSelectChange = new EventEmitter();
29965
29969
  this.types = InputType;
29970
+ this.states = ComponentStates;
29966
29971
  this.subscriptions = [];
29967
29972
  this.actionsCache = [];
29968
29973
  // Memo de getFieldProp/getUsernameProp por referencia de field — evita
@@ -29973,6 +29978,9 @@ class FormComponent {
29973
29978
  this.usernamePropMemo = new WeakMap();
29974
29979
  this.textareaPropMemo = new WeakMap();
29975
29980
  this.selectPropMemo = new WeakMap();
29981
+ this.previousUploadingCount = 0;
29982
+ // Campos ATTACHMENT con uploads en curso — bloquea submit + pone botón en WORKING
29983
+ this.uploadingFields = new Set();
29976
29984
  }
29977
29985
  ngOnInit() {
29978
29986
  const formControls = {};
@@ -29989,6 +29997,9 @@ class FormComponent {
29989
29997
  formControls[`${field.name}_from`] = [initialValue, field.validators || []];
29990
29998
  formControls[`${field.name}_to`] = [initialValue, field.validators || []];
29991
29999
  }
30000
+ else if (field.type === this.types.ATTACHMENT) {
30001
+ formControls[field.name] = [[], field.validators || []];
30002
+ }
29992
30003
  else {
29993
30004
  formControls[field.name] = [initialValue, field.validators || []];
29994
30005
  }
@@ -30019,8 +30030,12 @@ class FormComponent {
30019
30030
  }
30020
30031
  ngDoCheck() {
30021
30032
  // Detectar cambios en el estado del formulario (mutación sin cambiar referencia)
30022
- if (this.form && this.props && this.props.state !== this.previousState) {
30033
+ const uploadingCount = this.uploadingFields.size;
30034
+ if (this.form &&
30035
+ this.props &&
30036
+ (this.props.state !== this.previousState || uploadingCount !== this.previousUploadingCount)) {
30023
30037
  this.previousState = this.props.state;
30038
+ this.previousUploadingCount = uploadingCount;
30024
30039
  this.updateActionsCache();
30025
30040
  }
30026
30041
  }
@@ -30030,6 +30045,8 @@ class FormComponent {
30030
30045
  syncFieldControlStates() {
30031
30046
  this.props.sections.forEach(section => {
30032
30047
  section.fields.forEach(field => {
30048
+ if (field.type === this.types.ATTACHMENT)
30049
+ return;
30033
30050
  if (field.type === this.types.NUMBER_FROM_TO) {
30034
30051
  const fromControl = this.getControl(`${field.name}_from`);
30035
30052
  const toControl = this.getControl(`${field.name}_to`);
@@ -30059,7 +30076,7 @@ class FormComponent {
30059
30076
  */
30060
30077
  updateActionsCache() {
30061
30078
  let actionState = this.props.actions.state;
30062
- if (this.props.state === ComponentStates.WORKING) {
30079
+ if (this.props.state === ComponentStates.WORKING || this.uploadingFields.size > 0) {
30063
30080
  actionState = ComponentStates.WORKING;
30064
30081
  }
30065
30082
  else if (this.props.state === ComponentStates.DISABLED) {
@@ -30082,8 +30099,20 @@ class FormComponent {
30082
30099
  this.subscriptions.push(subscription);
30083
30100
  }
30084
30101
  async submitHandler(token) {
30102
+ if (this.uploadingFields.size > 0)
30103
+ return;
30085
30104
  this.onSubmit.emit({ fields: this.form.getRawValue(), token });
30086
30105
  }
30106
+ onAttachmentChange(fieldName, items) {
30107
+ const readyUrls = items.filter(a => a.status === 'ready' && a.url).map(a => a.url);
30108
+ this.form.get(fieldName)?.setValue(readyUrls, { emitEvent: false });
30109
+ if (items.some(a => a.status === 'uploading')) {
30110
+ this.uploadingFields.add(fieldName);
30111
+ }
30112
+ else {
30113
+ this.uploadingFields.delete(fieldName);
30114
+ }
30115
+ }
30087
30116
  getControl(field) {
30088
30117
  return this.Form.get(field);
30089
30118
  }
@@ -30211,7 +30240,7 @@ class FormComponent {
30211
30240
  <val-title [props]="{ content: s.name, size: 'large', color: '', bold: false }"></val-title>
30212
30241
  @for (f of s.fields; track f.name) {
30213
30242
  <div class="input" [attr.data-testid]="f.token">
30214
- @if (f.type !== types.PHONE && f.type !== types.HANDLE) {
30243
+ @if (f.type !== types.PHONE && f.type !== types.HANDLE && f.type !== types.ATTACHMENT) {
30215
30244
  <val-title [props]="{ content: f.label, size: 'small', color: 'dark', bold: false }"></val-title>
30216
30245
  }
30217
30246
  @if (f.description) {
@@ -30278,6 +30307,15 @@ class FormComponent {
30278
30307
  @case (types.CHECKBOX_RADIO) {
30279
30308
  <val-checkbox-radio-input [props]="getFieldProp(f)"></val-checkbox-radio-input>
30280
30309
  }
30310
+ @case (types.ATTACHMENT) {
30311
+ <val-attachment-uploader
30312
+ [props]="{
30313
+ uploadFn: f.uploadFn,
30314
+ disabled: f.state === states.DISABLED || f.state === states.WORKING,
30315
+ }"
30316
+ (attachmentsChange)="onAttachmentChange(f.name, $event)"
30317
+ ></val-attachment-uploader>
30318
+ }
30281
30319
  }
30282
30320
  <val-hint [props]="getFieldProp(f)"></val-hint>
30283
30321
  </div>
@@ -30292,7 +30330,7 @@ class FormComponent {
30292
30330
  ></val-button-group>
30293
30331
  </form>
30294
30332
  </div>
30295
- `, isInline: true, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-padding: 16px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}.section{margin-top:1rem}.input{margin:.5rem 0}@media (min-width: 768px){.input{margin:.75rem 0}}.field-description{display:block;font-size:.75rem;color:var(--ion-color-dark);margin-bottom:.25rem;line-height:1.4}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$7.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$7.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$7.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextInputComponent, selector: "val-text-input", inputs: ["preset", "props"] }, { kind: "component", type: TextareaInputComponent, selector: "val-textarea-input", inputs: ["preset", "props"] }, { kind: "component", type: CheckInputComponent, selector: "val-check-input", inputs: ["preset", "props"] }, { kind: "component", type: ButtonGroupComponent, selector: "val-button-group", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: DividerComponent, selector: "val-divider", inputs: ["props"] }, { kind: "component", type: HintComponent, selector: "val-hint", inputs: ["props"] }, { kind: "component", type: CommentInputComponent, selector: "val-comment-input", inputs: ["props"] }, { kind: "component", type: DateInputComponent, selector: "val-date-input", inputs: ["preset", "props"] }, { kind: "component", type: FileInputComponent, selector: "val-file-input", inputs: ["props"] }, { kind: "component", type: HourInputComponent, selector: "val-hour-input", inputs: ["props"] }, { kind: "component", type: EmailInputComponent, selector: "val-email-input", inputs: ["preset", "props"] }, { kind: "component", type: NumberInputComponent, selector: "val-number-input", inputs: ["props"] }, { kind: "component", type: NumberFromToComponent, selector: "val-number-from-to", inputs: ["props"] }, { kind: "component", type: RadioInputComponent, selector: "val-radio-input", inputs: ["props"] }, { kind: "component", type: PasswordInputComponent, selector: "val-password-input", inputs: ["preset", "props"] }, { kind: "component", type: PinInputComponent, selector: "val-pin-input", inputs: ["props"] }, { kind: "component", type: SelectSearchComponent, selector: "val-select-search", inputs: ["label", "labelProperty", "valueProperty", "multiple", "placeholder", "props"] }, { kind: "component", type: MultiSelectSearchComponent, selector: "val-multi-select-search", inputs: ["label", "labelProperty", "valueProperty", "placeholder", "props"] }, { kind: "component", type: SearchSelectorComponent, selector: "val-select-input", inputs: ["preset", "props"] }, { kind: "component", type: PhoneInputComponent, selector: "val-phone-input", inputs: ["preset", "props"], outputs: ["phoneChange"] }, { kind: "component", type: ToggleInputComponent, selector: "val-toggle-input", inputs: ["preset", "props"] }, { kind: "component", type: CheckboxRadioInputComponent, selector: "val-checkbox-radio-input", inputs: ["props"] }, { kind: "component", type: UsernameInputComponent, selector: "val-username-input", inputs: ["props"] }] }); }
30333
+ `, isInline: true, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-padding: 16px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}.section{margin-top:1rem}.input{margin:.5rem 0}@media (min-width: 768px){.input{margin:.75rem 0}}.field-description{display:block;font-size:.75rem;color:var(--ion-color-dark);margin-bottom:.25rem;line-height:1.4}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$7.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$7.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$7.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextInputComponent, selector: "val-text-input", inputs: ["preset", "props"] }, { kind: "component", type: TextareaInputComponent, selector: "val-textarea-input", inputs: ["preset", "props"] }, { kind: "component", type: CheckInputComponent, selector: "val-check-input", inputs: ["preset", "props"] }, { kind: "component", type: ButtonGroupComponent, selector: "val-button-group", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: DividerComponent, selector: "val-divider", inputs: ["props"] }, { kind: "component", type: HintComponent, selector: "val-hint", inputs: ["props"] }, { kind: "component", type: CommentInputComponent, selector: "val-comment-input", inputs: ["props"] }, { kind: "component", type: DateInputComponent, selector: "val-date-input", inputs: ["preset", "props"] }, { kind: "component", type: FileInputComponent, selector: "val-file-input", inputs: ["props"] }, { kind: "component", type: HourInputComponent, selector: "val-hour-input", inputs: ["props"] }, { kind: "component", type: EmailInputComponent, selector: "val-email-input", inputs: ["preset", "props"] }, { kind: "component", type: NumberInputComponent, selector: "val-number-input", inputs: ["props"] }, { kind: "component", type: NumberFromToComponent, selector: "val-number-from-to", inputs: ["props"] }, { kind: "component", type: RadioInputComponent, selector: "val-radio-input", inputs: ["props"] }, { kind: "component", type: PasswordInputComponent, selector: "val-password-input", inputs: ["preset", "props"] }, { kind: "component", type: PinInputComponent, selector: "val-pin-input", inputs: ["props"] }, { kind: "component", type: SelectSearchComponent, selector: "val-select-search", inputs: ["label", "labelProperty", "valueProperty", "multiple", "placeholder", "props"] }, { kind: "component", type: MultiSelectSearchComponent, selector: "val-multi-select-search", inputs: ["label", "labelProperty", "valueProperty", "placeholder", "props"] }, { kind: "component", type: SearchSelectorComponent, selector: "val-select-input", inputs: ["preset", "props"] }, { kind: "component", type: PhoneInputComponent, selector: "val-phone-input", inputs: ["preset", "props"], outputs: ["phoneChange"] }, { kind: "component", type: ToggleInputComponent, selector: "val-toggle-input", inputs: ["preset", "props"] }, { kind: "component", type: CheckboxRadioInputComponent, selector: "val-checkbox-radio-input", inputs: ["props"] }, { kind: "component", type: UsernameInputComponent, selector: "val-username-input", inputs: ["props"] }, { kind: "component", type: AttachmentUploaderComponent, selector: "val-attachment-uploader", inputs: ["props"], outputs: ["attachmentsChange"] }] }); }
30296
30334
  }
30297
30335
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FormComponent, decorators: [{
30298
30336
  type: Component,
@@ -30324,6 +30362,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
30324
30362
  ToggleInputComponent,
30325
30363
  CheckboxRadioInputComponent,
30326
30364
  UsernameInputComponent,
30365
+ AttachmentUploaderComponent,
30327
30366
  ], template: `
30328
30367
  <div class="container">
30329
30368
  <form [formGroup]="form">
@@ -30341,7 +30380,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
30341
30380
  <val-title [props]="{ content: s.name, size: 'large', color: '', bold: false }"></val-title>
30342
30381
  @for (f of s.fields; track f.name) {
30343
30382
  <div class="input" [attr.data-testid]="f.token">
30344
- @if (f.type !== types.PHONE && f.type !== types.HANDLE) {
30383
+ @if (f.type !== types.PHONE && f.type !== types.HANDLE && f.type !== types.ATTACHMENT) {
30345
30384
  <val-title [props]="{ content: f.label, size: 'small', color: 'dark', bold: false }"></val-title>
30346
30385
  }
30347
30386
  @if (f.description) {
@@ -30408,6 +30447,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
30408
30447
  @case (types.CHECKBOX_RADIO) {
30409
30448
  <val-checkbox-radio-input [props]="getFieldProp(f)"></val-checkbox-radio-input>
30410
30449
  }
30450
+ @case (types.ATTACHMENT) {
30451
+ <val-attachment-uploader
30452
+ [props]="{
30453
+ uploadFn: f.uploadFn,
30454
+ disabled: f.state === states.DISABLED || f.state === states.WORKING,
30455
+ }"
30456
+ (attachmentsChange)="onAttachmentChange(f.name, $event)"
30457
+ ></val-attachment-uploader>
30458
+ }
30411
30459
  }
30412
30460
  <val-hint [props]="getFieldProp(f)"></val-hint>
30413
30461
  </div>