valtech-components 4.0.80 → 4.0.82

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.80';
59
+ const VERSION = '4.0.82';
60
60
 
61
61
  // Control de estado de refresco (singleton a nivel de módulo)
62
62
  let isRefreshing = false;
@@ -39449,7 +39449,7 @@ class CompanyFooterComponent {
39449
39449
  </ion-row>
39450
39450
  </ion-grid>
39451
39451
  </footer>
39452
- `, isInline: true, styles: [":host{display:block}.withPadding{padding:16px}.withBackground{background-color:#ece5f6}:host-context(body.dark) .withBackground,:host-context(html.ion-palette-dark) .withBackground,:host-context([data-theme=\"dark\"]) .withBackground{background-color:#0e0420}.withRoundedTop{border-radius:var(--val-footer-top-radius, 24px) var(--val-footer-top-radius, 24px) 0 0;overflow:hidden}.borderless{border-top:none!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: IonRow, selector: "ion-row" }, { kind: "component", type: IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: RightsFooterComponent, selector: "val-rights-footer", inputs: ["props"] }, { kind: "component", type: FooterLinksComponent, selector: "val-footer-links", inputs: ["props"], outputs: ["logoClick"] }] }); }
39452
+ `, isInline: true, styles: [":host{display:block}.withPadding{padding:16px}.withBackground{background-color:#f5f0fb}:host-context(body.dark) .withBackground,:host-context(html.ion-palette-dark) .withBackground,:host-context([data-theme=\"dark\"]) .withBackground{background-color:#0e0420}.withRoundedTop{border-radius:var(--val-footer-top-radius, 24px) var(--val-footer-top-radius, 24px) 0 0;overflow:hidden}.borderless{border-top:none!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: IonRow, selector: "ion-row" }, { kind: "component", type: IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: RightsFooterComponent, selector: "val-rights-footer", inputs: ["props"] }, { kind: "component", type: FooterLinksComponent, selector: "val-footer-links", inputs: ["props"], outputs: ["logoClick"] }] }); }
39453
39453
  }
39454
39454
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CompanyFooterComponent, decorators: [{
39455
39455
  type: Component,
@@ -39471,7 +39471,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
39471
39471
  </ion-row>
39472
39472
  </ion-grid>
39473
39473
  </footer>
39474
- `, styles: [":host{display:block}.withPadding{padding:16px}.withBackground{background-color:#ece5f6}:host-context(body.dark) .withBackground,:host-context(html.ion-palette-dark) .withBackground,:host-context([data-theme=\"dark\"]) .withBackground{background-color:#0e0420}.withRoundedTop{border-radius:var(--val-footer-top-radius, 24px) var(--val-footer-top-radius, 24px) 0 0;overflow:hidden}.borderless{border-top:none!important}\n"] }]
39474
+ `, styles: [":host{display:block}.withPadding{padding:16px}.withBackground{background-color:#f5f0fb}:host-context(body.dark) .withBackground,:host-context(html.ion-palette-dark) .withBackground,:host-context([data-theme=\"dark\"]) .withBackground{background-color:#0e0420}.withRoundedTop{border-radius:var(--val-footer-top-radius, 24px) var(--val-footer-top-radius, 24px) 0 0;overflow:hidden}.borderless{border-top:none!important}\n"] }]
39475
39475
  }] });
39476
39476
 
39477
39477
  /**
@@ -64193,19 +64193,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
64193
64193
  * ```
64194
64194
  */
64195
64195
 
64196
+ const FEEDBACK_FORM_I18N = {
64197
+ es: {
64198
+ formTitle: 'Enviar feedback',
64199
+ formSubtitle: 'Cuéntanos qué piensas. Bugs, sugerencias y mejoras.',
64200
+ feedbackType: 'Tipo',
64201
+ title: 'Título',
64202
+ titlePlaceholder: 'Resumen corto (5–120 caracteres)',
64203
+ titleValidation: 'El título debe tener entre 5 y 120 caracteres',
64204
+ description: 'Descripción',
64205
+ descriptionPlaceholder: 'Describe el problema o sugerencia en detalle...',
64206
+ descriptionHint: 'Capturamos automáticamente tu navegador, SO y URL actual.',
64207
+ descriptionValidation: 'La descripción debe tener entre 10 y 2000 caracteres',
64208
+ submit: 'Enviar',
64209
+ feedbackSuccess: '¡Gracias por tu feedback!',
64210
+ feedbackError: 'No se pudo enviar el feedback. Inténtalo de nuevo.',
64211
+ },
64212
+ en: {
64213
+ formTitle: 'Send feedback',
64214
+ formSubtitle: 'Tell us what you think. Bugs, suggestions and improvements.',
64215
+ feedbackType: 'Type',
64216
+ title: 'Title',
64217
+ titlePlaceholder: 'Short summary (5–120 chars)',
64218
+ titleValidation: 'Title must be between 5 and 120 characters',
64219
+ description: 'Description',
64220
+ descriptionPlaceholder: 'Describe the problem or suggestion in detail...',
64221
+ descriptionHint: 'We automatically capture your browser, OS and current URL.',
64222
+ descriptionValidation: 'Description must be between 10 and 2000 characters',
64223
+ submit: 'Send',
64224
+ feedbackSuccess: 'Thanks for your feedback!',
64225
+ feedbackError: 'Could not send feedback. Please try again.',
64226
+ },
64227
+ };
64196
64228
  class FeedbackFormComponent {
64197
64229
  constructor() {
64198
64230
  this.props = {};
64199
64231
  this.onSubmit = new EventEmitter();
64200
64232
  this.onCancel = new EventEmitter();
64233
+ this.onError = new EventEmitter();
64201
64234
  this.i18n = inject(I18nService);
64202
64235
  this.feedbackService = inject(FeedbackService);
64236
+ this.toast = inject(ToastService);
64237
+ this.errors = inject(ValtechErrorService);
64203
64238
  this.typeOptions = DEFAULT_FEEDBACK_TYPE_OPTIONS;
64204
64239
  this.isSubmitting = signal(false);
64205
- this.isSuccess = signal(false);
64206
- this.error = signal(null);
64240
+ this.uploadFn = (file) => this.feedbackService.uploadAttachment(file);
64207
64241
  this.currentAttachments = [];
64208
- addIcons({ checkmarkCircleOutline, closeCircleOutline });
64242
+ this.i18n.registerDefaults('FeedbackForm', FEEDBACK_FORM_I18N);
64209
64243
  }
64210
64244
  ngOnInit() {
64211
64245
  if (this.props.enabledTypes?.length) {
@@ -64228,7 +64262,7 @@ class FeedbackFormComponent {
64228
64262
  fields.push({
64229
64263
  token: 'feedback-type',
64230
64264
  name: 'type',
64231
- label: this.i18n.t('feedbackType'),
64265
+ label: this.tt('feedbackType'),
64232
64266
  hint: '',
64233
64267
  placeholder: '',
64234
64268
  type: InputType.SELECT,
@@ -64243,32 +64277,32 @@ class FeedbackFormComponent {
64243
64277
  fields.push({
64244
64278
  token: 'feedback-title',
64245
64279
  name: 'title',
64246
- label: this.props.titleLabel ?? this.i18n.t('title'),
64280
+ label: this.props.titleLabel ?? this.tt('title'),
64247
64281
  hint: '',
64248
- placeholder: this.props.titlePlaceholder ?? this.i18n.t('titlePlaceholder'),
64282
+ placeholder: this.props.titlePlaceholder ?? this.tt('titlePlaceholder'),
64249
64283
  type: InputType.TEXT,
64250
64284
  order: 2,
64251
- validators: [Validators.required, Validators.minLength(5), Validators.maxLength(200)],
64285
+ validators: [Validators.required, Validators.minLength(5), Validators.maxLength(120)],
64252
64286
  errors: {
64253
- required: this.i18n.t('titleValidation'),
64254
- minlength: this.i18n.t('titleValidation'),
64255
- maxlength: this.i18n.t('titleValidation'),
64287
+ required: this.tt('titleValidation'),
64288
+ minlength: this.tt('titleValidation'),
64289
+ maxlength: this.tt('titleValidation'),
64256
64290
  },
64257
64291
  state: ComponentStates.ENABLED,
64258
64292
  }, {
64259
64293
  token: 'feedback-description',
64260
64294
  name: 'description',
64261
- label: this.props.descriptionLabel ?? this.i18n.t('description'),
64262
- hint: '',
64263
- placeholder: this.props.descriptionPlaceholder ?? this.i18n.t('descriptionPlaceholder'),
64295
+ label: this.props.descriptionLabel ?? this.tt('description'),
64296
+ hint: this.tt('descriptionHint'),
64297
+ placeholder: this.props.descriptionPlaceholder ?? this.tt('descriptionPlaceholder'),
64264
64298
  type: InputType.TEXTAREA,
64265
64299
  order: 3,
64266
64300
  range: { min: 10, max: 2000 },
64267
64301
  validators: [Validators.required, Validators.minLength(10), Validators.maxLength(2000)],
64268
64302
  errors: {
64269
- required: this.i18n.t('descriptionValidation'),
64270
- minlength: this.i18n.t('descriptionValidation'),
64271
- maxlength: this.i18n.t('descriptionValidation'),
64303
+ required: this.tt('descriptionValidation'),
64304
+ minlength: this.tt('descriptionValidation'),
64305
+ maxlength: this.tt('descriptionValidation'),
64272
64306
  },
64273
64307
  state: ComponentStates.ENABLED,
64274
64308
  });
@@ -64279,7 +64313,7 @@ class FeedbackFormComponent {
64279
64313
  type: 'submit',
64280
64314
  color: 'dark',
64281
64315
  shape: 'round',
64282
- text: this.props.submitButtonText ?? this.i18n.t('submit'),
64316
+ text: this.props.submitButtonText ?? this.tt('submit'),
64283
64317
  state: ComponentStates.ENABLED,
64284
64318
  expand: 'block',
64285
64319
  },
@@ -64292,8 +64326,6 @@ class FeedbackFormComponent {
64292
64326
  if (this.currentAttachments.some(a => a.status === 'uploading'))
64293
64327
  return;
64294
64328
  this.isSubmitting.set(true);
64295
- this.error.set(null);
64296
- this.isSuccess.set(false);
64297
64329
  this.formProps.state = ComponentStates.WORKING;
64298
64330
  this.formProps.actions.state = ComponentStates.WORKING;
64299
64331
  try {
@@ -64302,11 +64334,21 @@ class FeedbackFormComponent {
64302
64334
  const description = submitted.fields['description'];
64303
64335
  const attachmentUrls = this.currentAttachments.filter(a => a.status === 'ready').map(a => a.url);
64304
64336
  const response = await this.feedbackService.createAsync(type, title, description, attachmentUrls, this.props.contentRef);
64305
- this.isSuccess.set(true);
64337
+ this.toast.show({
64338
+ message: this.props.successMessage ?? this.tt('feedbackSuccess'),
64339
+ duration: 2500,
64340
+ color: 'dark',
64341
+ position: 'top',
64342
+ });
64306
64343
  this.onSubmit.emit({ response, type, title, attachmentUrls });
64307
64344
  }
64308
64345
  catch (err) {
64309
- this.error.set(err.error?.message || err.message || this.i18n.t('feedbackError'));
64346
+ this.errors.handle(err, {
64347
+ context: 'feedback-form.submit',
64348
+ fallbackKey: 'feedbackError',
64349
+ i18nNamespace: 'FeedbackForm',
64350
+ });
64351
+ this.onError.emit(err);
64310
64352
  }
64311
64353
  finally {
64312
64354
  this.isSubmitting.set(false);
@@ -64320,36 +64362,27 @@ class FeedbackFormComponent {
64320
64362
  onCancelClick() {
64321
64363
  this.onCancel.emit();
64322
64364
  }
64365
+ tt(key) {
64366
+ return this.i18n.t(key, 'FeedbackForm');
64367
+ }
64323
64368
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FeedbackFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
64324
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FeedbackFormComponent, isStandalone: true, selector: "val-feedback-form", inputs: { props: "props" }, outputs: { onSubmit: "onSubmit", onCancel: "onCancel" }, ngImport: i0, template: `
64369
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FeedbackFormComponent, isStandalone: true, selector: "val-feedback-form", inputs: { props: "props" }, outputs: { onSubmit: "onSubmit", onCancel: "onCancel", onError: "onError" }, ngImport: i0, template: `
64325
64370
  <div class="feedback-form-wrapper" [class.compact]="props.compact" [ngClass]="props.cssClass">
64326
64371
  @if (props.showTitle) {
64327
64372
  <div class="form-header">
64328
- <h2 class="form-title">{{ props.pageTitle || i18n.t('formTitle') }}</h2>
64329
- <p class="form-subtitle">{{ props.pageSubtitle || i18n.t('formSubtitle') }}</p>
64373
+ <val-display [props]="{ size: 'small', color: 'dark', content: props.pageTitle || tt('formTitle') }" />
64374
+ <val-title
64375
+ [props]="{ size: 'large', color: 'dark', bold: false, content: props.pageSubtitle || tt('formSubtitle') }"
64376
+ />
64330
64377
  </div>
64331
64378
  }
64332
64379
  <val-form [props]="formProps" (onSubmit)="handleFormSubmit($event)">
64333
64380
  @if (props.showAttachments !== false) {
64334
64381
  <val-attachment-uploader
64335
- [props]="{ maxFiles: 5 }"
64382
+ [props]="{ maxFiles: 5, uploadFn: uploadFn }"
64336
64383
  (attachmentsChange)="onAttachmentsChange($event)"
64337
64384
  ></val-attachment-uploader>
64338
64385
  }
64339
-
64340
- @if (error()) {
64341
- <div class="feedback-alert error">
64342
- <ion-icon name="close-circle-outline" aria-hidden="true"></ion-icon>
64343
- <span>{{ error() }}</span>
64344
- </div>
64345
- }
64346
-
64347
- @if (isSuccess()) {
64348
- <div class="feedback-alert success">
64349
- <ion-icon name="checkmark-circle-outline" aria-hidden="true"></ion-icon>
64350
- <span>{{ props.successMessage || i18n.t('feedbackSuccess') }}</span>
64351
- </div>
64352
- }
64353
64386
  </val-form>
64354
64387
 
64355
64388
  @if (props.cancelButtonText) {
@@ -64358,39 +64391,27 @@ class FeedbackFormComponent {
64358
64391
  </ion-button>
64359
64392
  }
64360
64393
  </div>
64361
- `, isInline: true, styles: [".feedback-form-wrapper{display:flex;flex-direction:column;gap:8px;&.compact{gap:4px}}.form-header{margin-bottom:8px}.form-title{font-size:1.5rem;font-weight:700;margin:0 0 4px;color:var(--ion-text-color)}.form-subtitle{font-size:1rem;font-weight:700;margin:0;color:var(--ion-text-color)}.feedback-alert{display:flex;align-items:center;gap:8px;padding:12px 16px;border-radius:8px;margin-top:8px;&.error{background-color:var(--ion-color-danger-tint);color:var(--ion-color-danger-shade)}&.success{background-color:var(--ion-color-success-tint);color:var(--ion-color-success-shade)}ion-icon{font-size:20px}}.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: 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"] }] }); }
64394
+ `, 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: 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"] }] }); }
64362
64395
  }
64363
64396
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FeedbackFormComponent, decorators: [{
64364
64397
  type: Component,
64365
- args: [{ selector: 'val-feedback-form', standalone: true, imports: [CommonModule, FormComponent, AttachmentUploaderComponent, IonButton, IonIcon], template: `
64398
+ args: [{ selector: 'val-feedback-form', standalone: true, imports: [CommonModule, FormComponent, AttachmentUploaderComponent, DisplayComponent, TitleComponent, IonButton], template: `
64366
64399
  <div class="feedback-form-wrapper" [class.compact]="props.compact" [ngClass]="props.cssClass">
64367
64400
  @if (props.showTitle) {
64368
64401
  <div class="form-header">
64369
- <h2 class="form-title">{{ props.pageTitle || i18n.t('formTitle') }}</h2>
64370
- <p class="form-subtitle">{{ props.pageSubtitle || i18n.t('formSubtitle') }}</p>
64402
+ <val-display [props]="{ size: 'small', color: 'dark', content: props.pageTitle || tt('formTitle') }" />
64403
+ <val-title
64404
+ [props]="{ size: 'large', color: 'dark', bold: false, content: props.pageSubtitle || tt('formSubtitle') }"
64405
+ />
64371
64406
  </div>
64372
64407
  }
64373
64408
  <val-form [props]="formProps" (onSubmit)="handleFormSubmit($event)">
64374
64409
  @if (props.showAttachments !== false) {
64375
64410
  <val-attachment-uploader
64376
- [props]="{ maxFiles: 5 }"
64411
+ [props]="{ maxFiles: 5, uploadFn: uploadFn }"
64377
64412
  (attachmentsChange)="onAttachmentsChange($event)"
64378
64413
  ></val-attachment-uploader>
64379
64414
  }
64380
-
64381
- @if (error()) {
64382
- <div class="feedback-alert error">
64383
- <ion-icon name="close-circle-outline" aria-hidden="true"></ion-icon>
64384
- <span>{{ error() }}</span>
64385
- </div>
64386
- }
64387
-
64388
- @if (isSuccess()) {
64389
- <div class="feedback-alert success">
64390
- <ion-icon name="checkmark-circle-outline" aria-hidden="true"></ion-icon>
64391
- <span>{{ props.successMessage || i18n.t('feedbackSuccess') }}</span>
64392
- </div>
64393
- }
64394
64415
  </val-form>
64395
64416
 
64396
64417
  @if (props.cancelButtonText) {
@@ -64399,13 +64420,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
64399
64420
  </ion-button>
64400
64421
  }
64401
64422
  </div>
64402
- `, styles: [".feedback-form-wrapper{display:flex;flex-direction:column;gap:8px;&.compact{gap:4px}}.form-header{margin-bottom:8px}.form-title{font-size:1.5rem;font-weight:700;margin:0 0 4px;color:var(--ion-text-color)}.form-subtitle{font-size:1rem;font-weight:700;margin:0;color:var(--ion-text-color)}.feedback-alert{display:flex;align-items:center;gap:8px;padding:12px 16px;border-radius:8px;margin-top:8px;&.error{background-color:var(--ion-color-danger-tint);color:var(--ion-color-danger-shade)}&.success{background-color:var(--ion-color-success-tint);color:var(--ion-color-success-shade)}ion-icon{font-size:20px}}.cancel-button{margin-top:8px}\n"] }]
64423
+ `, 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"] }]
64403
64424
  }], ctorParameters: () => [], propDecorators: { props: [{
64404
64425
  type: Input
64405
64426
  }], onSubmit: [{
64406
64427
  type: Output
64407
64428
  }], onCancel: [{
64408
64429
  type: Output
64430
+ }], onError: [{
64431
+ type: Output
64409
64432
  }] } });
64410
64433
 
64411
64434
  class RequestService {