valtech-components 4.0.84 → 4.0.86

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.
@@ -56,7 +56,7 @@ import { BrowserMultiFormatReader } from '@zxing/browser';
56
56
  * Current version of valtech-components.
57
57
  * This is automatically updated during the publish process.
58
58
  */
59
- const VERSION = '4.0.84';
59
+ const VERSION = '4.0.86';
60
60
 
61
61
  // Control de estado de refresco (singleton a nivel de módulo)
62
62
  let isRefreshing = false;
@@ -30976,11 +30976,11 @@ class AttachmentUploaderComponent {
30976
30976
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
30977
30977
  }
30978
30978
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AttachmentUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
30979
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AttachmentUploaderComponent, isStandalone: true, selector: "val-attachment-uploader", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { attachmentsChange: "attachmentsChange" }, ngImport: i0, template: "<div class=\"attachment-uploader\">\n @if (props().title) {\n <val-title class=\"attachment-title\" [props]=\"{ content: props().title, size: 'small', color: 'dark', bold: false }\" />\n }\n\n <input #filePicker type=\"file\" [accept]=\"accept()\" multiple class=\"hidden-input\" (change)=\"onFilesSelected($event)\" />\n\n <div class=\"attachment-actions\">\n <ion-button fill=\"outline\" color=\"dark\" size=\"small\" [disabled]=\"isDisabled()\" (click)=\"filePicker.click()\">\n <ion-icon slot=\"start\" name=\"attach-outline\"></ion-icon>\n {{ i18n.t('attachAdd') }}\n </ion-button>\n <!-- label trick: native browser gesture forwarding \u2192 iOS/PWA no bloquea el picker -->\n <label class=\"camera-label\" [class.camera-label--disabled]=\"isDisabled()\">\n <input\n type=\"file\"\n accept=\"image/*\"\n capture=\"environment\"\n class=\"hidden-input\"\n [disabled]=\"isDisabled()\"\n (change)=\"onFilesSelected($event)\"\n />\n <ion-icon name=\"camera-outline\"></ion-icon>\n {{ i18n.t('attachCamera') }}\n </label>\n </div>\n\n @if (maxReached()) {\n <ion-note color=\"warning\" class=\"max-note\">\n {{ i18n.t('attachMaxCount', '_global', { count: maxFiles().toString() }) }}\n </ion-note>\n } @if (attachments().length > 0) {\n <div class=\"attachment-list\">\n @for (item of attachments(); track item.id) {\n <div class=\"attachment-item\" [class]=\"'status-' + item.status\">\n <ion-icon\n class=\"file-icon\"\n [name]=\"item.file.type.startsWith('image/') ? 'image-outline' : 'document-outline'\"\n ></ion-icon>\n <div class=\"file-info\">\n <span class=\"file-name\">{{ item.file.name }}</span>\n <span class=\"file-size\">{{ formatSize(item.file.size) }}</span>\n </div>\n\n @if (item.status === 'uploading') {\n <ion-spinner class=\"status-icon\" name=\"circular\"></ion-spinner>\n } @else if (item.status === 'ready') {\n <ion-icon class=\"status-icon\" name=\"checkmark-circle-outline\" color=\"success\"></ion-icon>\n } @else if (item.status === 'error') {\n <ion-icon class=\"status-icon\" name=\"close-circle-outline\" color=\"danger\"></ion-icon>\n } @if (item.status !== 'uploading') {\n <ion-button fill=\"clear\" size=\"small\" color=\"dark\" (click)=\"remove(item.id)\">\n <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>\n </ion-button>\n } @if (item.status === 'error' && item.error) {\n <ion-note color=\"danger\" class=\"error-note\">{{ item.error }}</ion-note>\n }\n </div>\n }\n </div>\n } @if (props().hint) {\n <p class=\"attachment-hint\">{{ props().hint }}</p>\n }\n</div>\n", styles: [".hidden-input{display:none}.camera-label{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:0 14px;height:32px;box-sizing:border-box;font-size:.8125rem;font-weight:500;letter-spacing:.01em;border-radius:4px;border:1px solid currentColor;color:var(--ion-color-dark);background:transparent;cursor:pointer;-webkit-user-select:none;user-select:none;transition:opacity .15s}.camera-label ion-icon{font-size:1.125rem;flex-shrink:0}.camera-label--disabled{opacity:.4;pointer-events:none}.attachment-uploader{display:flex;flex-direction:column;gap:8px}.attachment-title{display:block;margin-bottom:2px}.attachment-hint{margin:2px 0 0;text-align:right;font-size:.75rem;line-height:1.4;color:var(--ion-color-dark)}.attachment-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.attachment-actions ion-button{margin:0;height:32px}.attachment-list{display:flex;flex-direction:column;gap:4px}.attachment-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:8px;background:var(--ion-background-color, #fff);border:1px solid var(--ion-color-light-shade, rgba(0, 0, 0, .08));flex-wrap:wrap}.attachment-item.status-error{border-color:var(--ion-color-danger)}.file-icon{font-size:1.375rem;color:var(--ion-text-color);flex-shrink:0}.file-info{flex:1;min-width:0;display:flex;flex-direction:column}.file-name{font-size:.8125rem;color:var(--ion-text-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-size{font-size:.6875rem;color:var(--ion-color-medium)}:host-context(.dark) .attachment-item,:host-context(.ion-palette-dark) .attachment-item,:host-context(html.ion-palette-dark) .attachment-item,:host-context(body.dark) .attachment-item,:host-context([data-theme=dark]) .attachment-item{background:#ffffff0a;border-color:#ffffff1f}:host-context(.dark) .attachment-item.status-error,:host-context(.ion-palette-dark) .attachment-item.status-error,:host-context(html.ion-palette-dark) .attachment-item.status-error,:host-context(body.dark) .attachment-item.status-error,:host-context([data-theme=dark]) .attachment-item.status-error{border-color:var(--ion-color-danger)}.status-icon{font-size:1.25rem;flex-shrink:0}.error-note{font-size:.6875rem;width:100%}.max-note{font-size:.75rem}\n"], dependencies: [{ kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
30979
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AttachmentUploaderComponent, isStandalone: true, selector: "val-attachment-uploader", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { attachmentsChange: "attachmentsChange" }, ngImport: i0, template: "<div class=\"attachment-uploader\">\n @if (props().title) {\n <val-title class=\"attachment-title\" [props]=\"{ content: props().title, size: 'small', color: 'dark', bold: false }\" />\n }\n\n <input #filePicker type=\"file\" [accept]=\"accept()\" multiple class=\"hidden-input\" (change)=\"onFilesSelected($event)\" />\n\n <div class=\"attachment-actions\">\n <ion-button fill=\"outline\" color=\"dark\" size=\"small\" [disabled]=\"isDisabled()\" (click)=\"filePicker.click()\">\n <ion-icon slot=\"start\" name=\"attach-outline\"></ion-icon>\n {{ i18n.t('attachAdd') }}\n </ion-button>\n <!-- label trick: native browser gesture forwarding \u2192 iOS/PWA no bloquea el picker -->\n <label class=\"camera-label\" [class.camera-label--disabled]=\"isDisabled()\">\n <input\n type=\"file\"\n accept=\"image/*\"\n capture=\"environment\"\n class=\"hidden-input\"\n [disabled]=\"isDisabled()\"\n (change)=\"onFilesSelected($event)\"\n />\n <ion-icon name=\"camera-outline\"></ion-icon>\n {{ i18n.t('attachCamera') }}\n </label>\n </div>\n\n @if (maxReached()) {\n <ion-note color=\"warning\" class=\"max-note\">\n {{ i18n.t('attachMaxCount', '_global', { count: maxFiles().toString() }) }}\n </ion-note>\n } @if (attachments().length > 0) {\n <div class=\"attachment-list\">\n @for (item of attachments(); track item.id) {\n <div class=\"attachment-item\" [class]=\"'status-' + item.status\">\n <ion-icon\n class=\"file-icon\"\n [name]=\"item.file.type.startsWith('image/') ? 'image-outline' : 'document-outline'\"\n ></ion-icon>\n <div class=\"file-info\">\n <span class=\"file-name\">{{ item.file.name }}</span>\n <span class=\"file-size\">{{ formatSize(item.file.size) }}</span>\n </div>\n\n @if (item.status === 'uploading') {\n <ion-spinner class=\"status-icon\" name=\"circular\"></ion-spinner>\n } @else if (item.status === 'ready') {\n <ion-icon class=\"status-icon\" name=\"checkmark-circle-outline\" color=\"success\"></ion-icon>\n } @else if (item.status === 'error') {\n <ion-icon class=\"status-icon\" name=\"close-circle-outline\" color=\"danger\"></ion-icon>\n } @if (item.status !== 'uploading') {\n <ion-button fill=\"clear\" size=\"small\" color=\"dark\" (click)=\"remove(item.id)\">\n <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>\n </ion-button>\n } @if (item.status === 'error' && item.error) {\n <ion-note color=\"danger\" class=\"error-note\">{{ item.error }}</ion-note>\n }\n </div>\n }\n </div>\n } @if (props().hint) {\n <p class=\"attachment-hint\">{{ props().hint }}</p>\n }\n</div>\n", styles: [".hidden-input{display:none}.camera-label{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:0 14px;height:32px;box-sizing:border-box;font-size:.8125rem;font-weight:500;letter-spacing:.01em;border-radius:4px;border:1px solid currentColor;color:var(--ion-color-dark);background:transparent;cursor:pointer;-webkit-user-select:none;user-select:none;transition:opacity .15s}@media (pointer: fine){.camera-label{display:none!important}}.camera-label ion-icon{font-size:1.125rem;flex-shrink:0}.camera-label--disabled{opacity:.4;pointer-events:none}.attachment-uploader{display:flex;flex-direction:column;gap:8px}.attachment-title{display:block;margin-bottom:2px}.attachment-hint{margin:2px 0 0;text-align:right;font-size:.75rem;line-height:1.4;color:var(--ion-color-dark)}.attachment-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.attachment-actions ion-button{margin:0;height:32px}.attachment-list{display:flex;flex-direction:column;gap:4px}.attachment-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:8px;background:var(--ion-background-color, #fff);border:1px solid var(--ion-color-light-shade, rgba(0, 0, 0, .08));flex-wrap:wrap}.attachment-item.status-error{border-color:var(--ion-color-danger)}.file-icon{font-size:1.375rem;color:var(--ion-text-color);flex-shrink:0}.file-info{flex:1;min-width:0;display:flex;flex-direction:column}.file-name{font-size:.8125rem;color:var(--ion-text-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-size{font-size:.6875rem;color:var(--ion-color-medium)}:host-context(.dark) .attachment-item,:host-context(.ion-palette-dark) .attachment-item,:host-context(html.ion-palette-dark) .attachment-item,:host-context(body.dark) .attachment-item,:host-context([data-theme=dark]) .attachment-item{background:#ffffff0a;border-color:#ffffff1f}:host-context(.dark) .attachment-item.status-error,:host-context(.ion-palette-dark) .attachment-item.status-error,:host-context(html.ion-palette-dark) .attachment-item.status-error,:host-context(body.dark) .attachment-item.status-error,:host-context([data-theme=dark]) .attachment-item.status-error{border-color:var(--ion-color-danger)}.status-icon{font-size:1.25rem;flex-shrink:0}.error-note{font-size:.6875rem;width:100%}.max-note{font-size:.75rem}\n"], dependencies: [{ kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
30980
30980
  }
30981
30981
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AttachmentUploaderComponent, decorators: [{
30982
30982
  type: Component,
30983
- args: [{ selector: 'val-attachment-uploader', standalone: true, imports: [IonButton, IonIcon, IonNote, IonSpinner, TitleComponent], template: "<div class=\"attachment-uploader\">\n @if (props().title) {\n <val-title class=\"attachment-title\" [props]=\"{ content: props().title, size: 'small', color: 'dark', bold: false }\" />\n }\n\n <input #filePicker type=\"file\" [accept]=\"accept()\" multiple class=\"hidden-input\" (change)=\"onFilesSelected($event)\" />\n\n <div class=\"attachment-actions\">\n <ion-button fill=\"outline\" color=\"dark\" size=\"small\" [disabled]=\"isDisabled()\" (click)=\"filePicker.click()\">\n <ion-icon slot=\"start\" name=\"attach-outline\"></ion-icon>\n {{ i18n.t('attachAdd') }}\n </ion-button>\n <!-- label trick: native browser gesture forwarding \u2192 iOS/PWA no bloquea el picker -->\n <label class=\"camera-label\" [class.camera-label--disabled]=\"isDisabled()\">\n <input\n type=\"file\"\n accept=\"image/*\"\n capture=\"environment\"\n class=\"hidden-input\"\n [disabled]=\"isDisabled()\"\n (change)=\"onFilesSelected($event)\"\n />\n <ion-icon name=\"camera-outline\"></ion-icon>\n {{ i18n.t('attachCamera') }}\n </label>\n </div>\n\n @if (maxReached()) {\n <ion-note color=\"warning\" class=\"max-note\">\n {{ i18n.t('attachMaxCount', '_global', { count: maxFiles().toString() }) }}\n </ion-note>\n } @if (attachments().length > 0) {\n <div class=\"attachment-list\">\n @for (item of attachments(); track item.id) {\n <div class=\"attachment-item\" [class]=\"'status-' + item.status\">\n <ion-icon\n class=\"file-icon\"\n [name]=\"item.file.type.startsWith('image/') ? 'image-outline' : 'document-outline'\"\n ></ion-icon>\n <div class=\"file-info\">\n <span class=\"file-name\">{{ item.file.name }}</span>\n <span class=\"file-size\">{{ formatSize(item.file.size) }}</span>\n </div>\n\n @if (item.status === 'uploading') {\n <ion-spinner class=\"status-icon\" name=\"circular\"></ion-spinner>\n } @else if (item.status === 'ready') {\n <ion-icon class=\"status-icon\" name=\"checkmark-circle-outline\" color=\"success\"></ion-icon>\n } @else if (item.status === 'error') {\n <ion-icon class=\"status-icon\" name=\"close-circle-outline\" color=\"danger\"></ion-icon>\n } @if (item.status !== 'uploading') {\n <ion-button fill=\"clear\" size=\"small\" color=\"dark\" (click)=\"remove(item.id)\">\n <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>\n </ion-button>\n } @if (item.status === 'error' && item.error) {\n <ion-note color=\"danger\" class=\"error-note\">{{ item.error }}</ion-note>\n }\n </div>\n }\n </div>\n } @if (props().hint) {\n <p class=\"attachment-hint\">{{ props().hint }}</p>\n }\n</div>\n", styles: [".hidden-input{display:none}.camera-label{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:0 14px;height:32px;box-sizing:border-box;font-size:.8125rem;font-weight:500;letter-spacing:.01em;border-radius:4px;border:1px solid currentColor;color:var(--ion-color-dark);background:transparent;cursor:pointer;-webkit-user-select:none;user-select:none;transition:opacity .15s}.camera-label ion-icon{font-size:1.125rem;flex-shrink:0}.camera-label--disabled{opacity:.4;pointer-events:none}.attachment-uploader{display:flex;flex-direction:column;gap:8px}.attachment-title{display:block;margin-bottom:2px}.attachment-hint{margin:2px 0 0;text-align:right;font-size:.75rem;line-height:1.4;color:var(--ion-color-dark)}.attachment-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.attachment-actions ion-button{margin:0;height:32px}.attachment-list{display:flex;flex-direction:column;gap:4px}.attachment-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:8px;background:var(--ion-background-color, #fff);border:1px solid var(--ion-color-light-shade, rgba(0, 0, 0, .08));flex-wrap:wrap}.attachment-item.status-error{border-color:var(--ion-color-danger)}.file-icon{font-size:1.375rem;color:var(--ion-text-color);flex-shrink:0}.file-info{flex:1;min-width:0;display:flex;flex-direction:column}.file-name{font-size:.8125rem;color:var(--ion-text-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-size{font-size:.6875rem;color:var(--ion-color-medium)}:host-context(.dark) .attachment-item,:host-context(.ion-palette-dark) .attachment-item,:host-context(html.ion-palette-dark) .attachment-item,:host-context(body.dark) .attachment-item,:host-context([data-theme=dark]) .attachment-item{background:#ffffff0a;border-color:#ffffff1f}:host-context(.dark) .attachment-item.status-error,:host-context(.ion-palette-dark) .attachment-item.status-error,:host-context(html.ion-palette-dark) .attachment-item.status-error,:host-context(body.dark) .attachment-item.status-error,:host-context([data-theme=dark]) .attachment-item.status-error{border-color:var(--ion-color-danger)}.status-icon{font-size:1.25rem;flex-shrink:0}.error-note{font-size:.6875rem;width:100%}.max-note{font-size:.75rem}\n"] }]
30983
+ args: [{ selector: 'val-attachment-uploader', standalone: true, imports: [IonButton, IonIcon, IonNote, IonSpinner, TitleComponent], template: "<div class=\"attachment-uploader\">\n @if (props().title) {\n <val-title class=\"attachment-title\" [props]=\"{ content: props().title, size: 'small', color: 'dark', bold: false }\" />\n }\n\n <input #filePicker type=\"file\" [accept]=\"accept()\" multiple class=\"hidden-input\" (change)=\"onFilesSelected($event)\" />\n\n <div class=\"attachment-actions\">\n <ion-button fill=\"outline\" color=\"dark\" size=\"small\" [disabled]=\"isDisabled()\" (click)=\"filePicker.click()\">\n <ion-icon slot=\"start\" name=\"attach-outline\"></ion-icon>\n {{ i18n.t('attachAdd') }}\n </ion-button>\n <!-- label trick: native browser gesture forwarding \u2192 iOS/PWA no bloquea el picker -->\n <label class=\"camera-label\" [class.camera-label--disabled]=\"isDisabled()\">\n <input\n type=\"file\"\n accept=\"image/*\"\n capture=\"environment\"\n class=\"hidden-input\"\n [disabled]=\"isDisabled()\"\n (change)=\"onFilesSelected($event)\"\n />\n <ion-icon name=\"camera-outline\"></ion-icon>\n {{ i18n.t('attachCamera') }}\n </label>\n </div>\n\n @if (maxReached()) {\n <ion-note color=\"warning\" class=\"max-note\">\n {{ i18n.t('attachMaxCount', '_global', { count: maxFiles().toString() }) }}\n </ion-note>\n } @if (attachments().length > 0) {\n <div class=\"attachment-list\">\n @for (item of attachments(); track item.id) {\n <div class=\"attachment-item\" [class]=\"'status-' + item.status\">\n <ion-icon\n class=\"file-icon\"\n [name]=\"item.file.type.startsWith('image/') ? 'image-outline' : 'document-outline'\"\n ></ion-icon>\n <div class=\"file-info\">\n <span class=\"file-name\">{{ item.file.name }}</span>\n <span class=\"file-size\">{{ formatSize(item.file.size) }}</span>\n </div>\n\n @if (item.status === 'uploading') {\n <ion-spinner class=\"status-icon\" name=\"circular\"></ion-spinner>\n } @else if (item.status === 'ready') {\n <ion-icon class=\"status-icon\" name=\"checkmark-circle-outline\" color=\"success\"></ion-icon>\n } @else if (item.status === 'error') {\n <ion-icon class=\"status-icon\" name=\"close-circle-outline\" color=\"danger\"></ion-icon>\n } @if (item.status !== 'uploading') {\n <ion-button fill=\"clear\" size=\"small\" color=\"dark\" (click)=\"remove(item.id)\">\n <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>\n </ion-button>\n } @if (item.status === 'error' && item.error) {\n <ion-note color=\"danger\" class=\"error-note\">{{ item.error }}</ion-note>\n }\n </div>\n }\n </div>\n } @if (props().hint) {\n <p class=\"attachment-hint\">{{ props().hint }}</p>\n }\n</div>\n", styles: [".hidden-input{display:none}.camera-label{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:0 14px;height:32px;box-sizing:border-box;font-size:.8125rem;font-weight:500;letter-spacing:.01em;border-radius:4px;border:1px solid currentColor;color:var(--ion-color-dark);background:transparent;cursor:pointer;-webkit-user-select:none;user-select:none;transition:opacity .15s}@media (pointer: fine){.camera-label{display:none!important}}.camera-label ion-icon{font-size:1.125rem;flex-shrink:0}.camera-label--disabled{opacity:.4;pointer-events:none}.attachment-uploader{display:flex;flex-direction:column;gap:8px}.attachment-title{display:block;margin-bottom:2px}.attachment-hint{margin:2px 0 0;text-align:right;font-size:.75rem;line-height:1.4;color:var(--ion-color-dark)}.attachment-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.attachment-actions ion-button{margin:0;height:32px}.attachment-list{display:flex;flex-direction:column;gap:4px}.attachment-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:8px;background:var(--ion-background-color, #fff);border:1px solid var(--ion-color-light-shade, rgba(0, 0, 0, .08));flex-wrap:wrap}.attachment-item.status-error{border-color:var(--ion-color-danger)}.file-icon{font-size:1.375rem;color:var(--ion-text-color);flex-shrink:0}.file-info{flex:1;min-width:0;display:flex;flex-direction:column}.file-name{font-size:.8125rem;color:var(--ion-text-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-size{font-size:.6875rem;color:var(--ion-color-medium)}:host-context(.dark) .attachment-item,:host-context(.ion-palette-dark) .attachment-item,:host-context(html.ion-palette-dark) .attachment-item,:host-context(body.dark) .attachment-item,:host-context([data-theme=dark]) .attachment-item{background:#ffffff0a;border-color:#ffffff1f}:host-context(.dark) .attachment-item.status-error,:host-context(.ion-palette-dark) .attachment-item.status-error,:host-context(html.ion-palette-dark) .attachment-item.status-error,:host-context(body.dark) .attachment-item.status-error,:host-context([data-theme=dark]) .attachment-item.status-error{border-color:var(--ion-color-danger)}.status-icon{font-size:1.25rem;flex-shrink:0}.error-note{font-size:.6875rem;width:100%}.max-note{font-size:.75rem}\n"] }]
30984
30984
  }], ctorParameters: () => [] });
30985
30985
 
30986
30986
  /**
@@ -64206,6 +64206,8 @@ const FEEDBACK_FORM_I18N = {
64206
64206
  descriptionHint: 'Capturamos automáticamente tu navegador, SO y URL actual.',
64207
64207
  descriptionValidation: 'La descripción debe tener entre 10 y 2000 caracteres',
64208
64208
  submit: 'Enviar',
64209
+ attachmentsLabel: 'Archivos adjuntos (opcional)',
64210
+ attachmentsHint: 'Imágenes (JPG/PNG/WebP) o PDF, máx 5 MB cada uno. Hasta 5 archivos.',
64209
64211
  feedbackSuccess: '¡Gracias por tu feedback!',
64210
64212
  feedbackError: 'No se pudo enviar el feedback. Inténtalo de nuevo.',
64211
64213
  },
@@ -64221,6 +64223,8 @@ const FEEDBACK_FORM_I18N = {
64221
64223
  descriptionHint: 'We automatically capture your browser, OS and current URL.',
64222
64224
  descriptionValidation: 'Description must be between 10 and 2000 characters',
64223
64225
  submit: 'Send',
64226
+ attachmentsLabel: 'Attachments (optional)',
64227
+ attachmentsHint: 'Images (JPG/PNG/WebP) or PDF, max 5 MB each. Up to 5 files.',
64224
64228
  feedbackSuccess: 'Thanks for your feedback!',
64225
64229
  feedbackError: 'Could not send feedback. Please try again.',
64226
64230
  },
@@ -64237,8 +64241,6 @@ class FeedbackFormComponent {
64237
64241
  this.errors = inject(ValtechErrorService);
64238
64242
  this.typeOptions = DEFAULT_FEEDBACK_TYPE_OPTIONS;
64239
64243
  this.isSubmitting = signal(false);
64240
- this.uploadFn = (file) => this.feedbackService.uploadAttachment(file);
64241
- this.currentAttachments = [];
64242
64244
  this.i18n.registerDefaults('FeedbackForm', FEEDBACK_FORM_I18N);
64243
64245
  }
64244
64246
  ngOnInit() {
@@ -64289,7 +64291,8 @@ class FeedbackFormComponent {
64289
64291
  maxlength: this.tt('titleValidation'),
64290
64292
  },
64291
64293
  state: ComponentStates.ENABLED,
64292
- }, {
64294
+ });
64295
+ fields.push({
64293
64296
  token: 'feedback-description',
64294
64297
  name: 'description',
64295
64298
  label: this.props.descriptionLabel ?? this.tt('description'),
@@ -64306,6 +64309,21 @@ class FeedbackFormComponent {
64306
64309
  },
64307
64310
  state: ComponentStates.ENABLED,
64308
64311
  });
64312
+ if (this.props.showAttachments !== false) {
64313
+ fields.push({
64314
+ token: 'feedback-attachments',
64315
+ name: 'attachments',
64316
+ label: this.props.attachmentsLabel ?? this.tt('attachmentsLabel'),
64317
+ hint: this.tt('attachmentsHint'),
64318
+ placeholder: '',
64319
+ type: InputType.ATTACHMENT,
64320
+ order: 4,
64321
+ validators: [],
64322
+ errors: {},
64323
+ state: ComponentStates.ENABLED,
64324
+ uploadFn: (file) => this.feedbackService.uploadAttachment(file),
64325
+ });
64326
+ }
64309
64327
  return {
64310
64328
  name: '',
64311
64329
  sections: [{ name: '', order: 0, fields }],
@@ -64323,8 +64341,6 @@ class FeedbackFormComponent {
64323
64341
  async handleFormSubmit(submitted) {
64324
64342
  if (this.isSubmitting())
64325
64343
  return;
64326
- if (this.currentAttachments.some(a => a.status === 'uploading'))
64327
- return;
64328
64344
  this.isSubmitting.set(true);
64329
64345
  this.formProps.state = ComponentStates.WORKING;
64330
64346
  this.formProps.actions.state = ComponentStates.WORKING;
@@ -64332,7 +64348,7 @@ class FeedbackFormComponent {
64332
64348
  const type = submitted.fields['type'] ?? this.props.defaultType ?? 'feedback';
64333
64349
  const title = submitted.fields['title'];
64334
64350
  const description = submitted.fields['description'];
64335
- const attachmentUrls = this.currentAttachments.filter(a => a.status === 'ready').map(a => a.url);
64351
+ const attachmentUrls = submitted.fields['attachments'] ?? [];
64336
64352
  const response = await this.feedbackService.createAsync(type, title, description, attachmentUrls, this.props.contentRef);
64337
64353
  this.toast.show({
64338
64354
  message: this.props.successMessage ?? this.tt('feedbackSuccess'),
@@ -64356,9 +64372,6 @@ class FeedbackFormComponent {
64356
64372
  this.formProps.actions.state = ComponentStates.ENABLED;
64357
64373
  }
64358
64374
  }
64359
- onAttachmentsChange(items) {
64360
- this.currentAttachments = items;
64361
- }
64362
64375
  onCancelClick() {
64363
64376
  this.onCancel.emit();
64364
64377
  }
@@ -64376,14 +64389,7 @@ class FeedbackFormComponent {
64376
64389
  />
64377
64390
  </div>
64378
64391
  }
64379
- <val-form [props]="formProps" (onSubmit)="handleFormSubmit($event)">
64380
- @if (props.showAttachments !== false) {
64381
- <val-attachment-uploader
64382
- [props]="{ maxFiles: 5, uploadFn: uploadFn }"
64383
- (attachmentsChange)="onAttachmentsChange($event)"
64384
- ></val-attachment-uploader>
64385
- }
64386
- </val-form>
64392
+ <val-form [props]="formProps" (onSubmit)="handleFormSubmit($event)" />
64387
64393
 
64388
64394
  @if (props.cancelButtonText) {
64389
64395
  <ion-button fill="outline" color="medium" expand="block" class="cancel-button" (click)="onCancelClick()">
@@ -64391,11 +64397,11 @@ class FeedbackFormComponent {
64391
64397
  </ion-button>
64392
64398
  }
64393
64399
  </div>
64394
- `, isInline: true, styles: [".feedback-form-wrapper{display:flex;flex-direction:column;gap:8px;&.compact{gap:4px}}.form-header{margin-bottom:8px}val-attachment-uploader{display:block;margin-top:16px;margin-bottom:8px}.cancel-button{margin-top:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: AttachmentUploaderComponent, selector: "val-attachment-uploader", inputs: ["props"], outputs: ["attachmentsChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }] }); }
64400
+ `, isInline: true, styles: [".feedback-form-wrapper{display:flex;flex-direction:column;gap:8px;&.compact{gap:4px}}.form-header{margin-bottom:8px}.cancel-button{margin-top:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }] }); }
64395
64401
  }
64396
64402
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FeedbackFormComponent, decorators: [{
64397
64403
  type: Component,
64398
- args: [{ selector: 'val-feedback-form', standalone: true, imports: [CommonModule, FormComponent, AttachmentUploaderComponent, DisplayComponent, TitleComponent, IonButton], template: `
64404
+ args: [{ selector: 'val-feedback-form', standalone: true, imports: [CommonModule, FormComponent, DisplayComponent, TitleComponent, IonButton], template: `
64399
64405
  <div class="feedback-form-wrapper" [class.compact]="props.compact" [ngClass]="props.cssClass">
64400
64406
  @if (props.showTitle) {
64401
64407
  <div class="form-header">
@@ -64405,14 +64411,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
64405
64411
  />
64406
64412
  </div>
64407
64413
  }
64408
- <val-form [props]="formProps" (onSubmit)="handleFormSubmit($event)">
64409
- @if (props.showAttachments !== false) {
64410
- <val-attachment-uploader
64411
- [props]="{ maxFiles: 5, uploadFn: uploadFn }"
64412
- (attachmentsChange)="onAttachmentsChange($event)"
64413
- ></val-attachment-uploader>
64414
- }
64415
- </val-form>
64414
+ <val-form [props]="formProps" (onSubmit)="handleFormSubmit($event)" />
64416
64415
 
64417
64416
  @if (props.cancelButtonText) {
64418
64417
  <ion-button fill="outline" color="medium" expand="block" class="cancel-button" (click)="onCancelClick()">
@@ -64420,7 +64419,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
64420
64419
  </ion-button>
64421
64420
  }
64422
64421
  </div>
64423
- `, styles: [".feedback-form-wrapper{display:flex;flex-direction:column;gap:8px;&.compact{gap:4px}}.form-header{margin-bottom:8px}val-attachment-uploader{display:block;margin-top:16px;margin-bottom:8px}.cancel-button{margin-top:8px}\n"] }]
64422
+ `, styles: [".feedback-form-wrapper{display:flex;flex-direction:column;gap:8px;&.compact{gap:4px}}.form-header{margin-bottom:8px}.cancel-button{margin-top:8px}\n"] }]
64424
64423
  }], ctorParameters: () => [], propDecorators: { props: [{
64425
64424
  type: Input
64426
64425
  }], onSubmit: [{