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.
- package/esm2022/lib/components/molecules/feedback-form/feedback-form.component.mjs +28 -30
- package/esm2022/lib/components/organisms/attachment-uploader/attachment-uploader.component.mjs +3 -3
- package/esm2022/lib/version.mjs +2 -2
- package/fesm2022/valtech-components.mjs +30 -31
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/molecules/feedback-form/feedback-form.component.d.ts +0 -4
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -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.
|
|
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 =
|
|
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}
|
|
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,
|
|
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}
|
|
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: [{
|