valtech-components 4.0.1006 → 4.0.1008

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.
Files changed (21) hide show
  1. package/esm2022/lib/components/organisms/field-schema-editor/field-schema-editor.component.mjs +14 -1
  2. package/esm2022/lib/components/organisms/field-schema-editor/field-schema-editor.i18n.mjs +5 -1
  3. package/esm2022/lib/components/organisms/field-schema-editor/types.mjs +1 -1
  4. package/esm2022/lib/components/organisms/survey-builder/survey-builder.component.mjs +72 -9
  5. package/esm2022/lib/components/organisms/survey-builder/survey-builder.i18n.mjs +9 -1
  6. package/esm2022/lib/components/organisms/survey-builder/survey-preview-modal.component.mjs +129 -0
  7. package/esm2022/lib/components/organisms/survey-response/survey-response.component.mjs +150 -12
  8. package/esm2022/lib/components/organisms/survey-response/survey-response.i18n.mjs +7 -1
  9. package/esm2022/lib/services/forms/form-schema-builder.service.mjs +18 -4
  10. package/esm2022/lib/services/forms/types.mjs +1 -1
  11. package/esm2022/lib/version.mjs +2 -2
  12. package/fesm2022/valtech-components.mjs +389 -22
  13. package/fesm2022/valtech-components.mjs.map +1 -1
  14. package/lib/components/organisms/field-schema-editor/field-schema-editor.component.d.ts +1 -0
  15. package/lib/components/organisms/field-schema-editor/types.d.ts +2 -0
  16. package/lib/components/organisms/survey-builder/survey-builder.component.d.ts +5 -0
  17. package/lib/components/organisms/survey-builder/survey-preview-modal.component.d.ts +28 -0
  18. package/lib/components/organisms/survey-response/survey-response.component.d.ts +11 -1
  19. package/lib/services/forms/types.d.ts +6 -0
  20. package/lib/version.d.ts +1 -1
  21. package/package.json +1 -1
@@ -70,7 +70,7 @@ import fixWebmDuration from 'fix-webm-duration';
70
70
  * Current version of valtech-components.
71
71
  * This is automatically updated during the publish process.
72
72
  */
73
- const VERSION = '4.0.1006';
73
+ const VERSION = '4.0.1008';
74
74
 
75
75
  function evaluateValtechAccess(rule, context, features = {}, visitedFeatures = new Set()) {
76
76
  if (rule == null)
@@ -39116,11 +39116,22 @@ class FormSchemaBuilderService {
39116
39116
  }
39117
39117
  buildForm(schema, i18nNamespace, formName, submitLabelKey = 'submit', state = ComponentStates.ENABLED) {
39118
39118
  const sorted = [...schema].sort((a, b) => a.order - b.order);
39119
- const fields = sorted.map(def => this.buildField(def, i18nNamespace, state));
39120
- const section = { name: '', order: 0, fields };
39119
+ const sectionsById = new Map();
39120
+ sorted.forEach(def => {
39121
+ const key = def.sectionId || def.sectionLabel || '';
39122
+ const section = sectionsById.get(key) ??
39123
+ {
39124
+ name: def.sectionLabel || '',
39125
+ order: def.sectionOrder ?? 0,
39126
+ fields: [],
39127
+ };
39128
+ section.fields.push(this.buildField(def, i18nNamespace, state));
39129
+ sectionsById.set(key, section);
39130
+ });
39131
+ const sections = [...sectionsById.values()].sort((a, b) => a.order - b.order);
39121
39132
  return {
39122
39133
  name: formName,
39123
- sections: [section],
39134
+ sections,
39124
39135
  actions: button({
39125
39136
  text: this.i18n.t(submitLabelKey, i18nNamespace),
39126
39137
  type: 'submit',
@@ -39195,6 +39206,9 @@ class FormSchemaBuilderService {
39195
39206
  required: !!field.required,
39196
39207
  validators,
39197
39208
  options: this.normalizeOptions(field.options),
39209
+ sectionId: field.sectionId,
39210
+ sectionLabel: field.sectionLabel?.trim() ?? '',
39211
+ sectionOrder: field.sectionOrder,
39198
39212
  step: field.step,
39199
39213
  unitLabel: field.unitLabel,
39200
39214
  numberPickerVariant: field.numberPickerVariant,
@@ -39399,6 +39413,9 @@ const SURVEY_RESPONSE_I18N = {
39399
39413
  required: 'Esta pregunta es obligatoria',
39400
39414
  yes: 'Sí',
39401
39415
  no: 'No',
39416
+ previous: 'Volver',
39417
+ next: 'Siguiente',
39418
+ stepsLabel: 'Pasos de la encuesta',
39402
39419
  submit: 'Enviar',
39403
39420
  alreadyTitle: 'Ya respondiste esta encuesta',
39404
39421
  alreadyBody: 'Tu respuesta quedó registrada la primera vez. Gracias por tu tiempo.',
@@ -39422,6 +39439,9 @@ const SURVEY_RESPONSE_I18N = {
39422
39439
  required: 'This question is required',
39423
39440
  yes: 'Yes',
39424
39441
  no: 'No',
39442
+ previous: 'Back',
39443
+ next: 'Next',
39444
+ stepsLabel: 'Survey steps',
39425
39445
  submit: 'Submit',
39426
39446
  alreadyTitle: 'You already answered this survey',
39427
39447
  alreadyBody: 'Your response was recorded the first time. Thanks for your time.',
@@ -39479,6 +39499,8 @@ class SurveyResponseComponent {
39479
39499
  this.sending = signal(false);
39480
39500
  this.typeConfig = signal(null);
39481
39501
  this.alreadyAnswered = signal(false);
39502
+ this.responseStep = signal(0);
39503
+ this.responseValues = signal({});
39482
39504
  this.nameControl = new FormControl('', { nonNullable: true });
39483
39505
  this.emailControl = new FormControl('', { nonNullable: true });
39484
39506
  this.form = new FormGroup({});
@@ -39487,7 +39509,20 @@ class SurveyResponseComponent {
39487
39509
  if (!cfg)
39488
39510
  return null;
39489
39511
  const form = this.formSchemas.buildForm(this.surveySchemaForForm(cfg.fieldSchema), NAMESPACE$4, '', 'submit', this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED);
39490
- return { ...form, showDividers: false, fieldSpacing: 16 };
39512
+ const sections = this.hasResponseSteps() ? [this.sectionWithStoredValues(this.responseSections()[this.responseStep()])] : form.sections;
39513
+ return {
39514
+ ...form,
39515
+ sections,
39516
+ showDividers: false,
39517
+ fieldSpacing: 16,
39518
+ controlled: this.hasResponseSteps(),
39519
+ };
39520
+ });
39521
+ this.responseSections = computed(() => {
39522
+ const cfg = this.typeConfig();
39523
+ if (!cfg)
39524
+ return [];
39525
+ return this.formSchemas.buildForm(this.surveySchemaForForm(cfg.fieldSchema), NAMESPACE$4, '', 'submit').sections;
39491
39526
  });
39492
39527
  /** Preguntas ordenadas, expuestas para tests/hosts que inspeccionan el schema resuelto. */
39493
39528
  this.questions = computed(() => {
@@ -39565,6 +39600,19 @@ class SurveyResponseComponent {
39565
39600
  }
39566
39601
  this.form = new FormGroup(group);
39567
39602
  }
39603
+ hasResponseSteps() {
39604
+ return this.responseSections().filter(section => !!section.name).length > 1;
39605
+ }
39606
+ sectionWithStoredValues(section) {
39607
+ const values = this.responseValues();
39608
+ return {
39609
+ ...section,
39610
+ fields: section.fields.map(field => ({
39611
+ ...field,
39612
+ value: Object.prototype.hasOwnProperty.call(values, field.name) ? values[field.name] : field.value,
39613
+ })),
39614
+ };
39615
+ }
39568
39616
  questionControl(name) {
39569
39617
  return (this.formComponent?.Form.get(name) ?? this.form.get(name));
39570
39618
  }
@@ -39650,27 +39698,39 @@ class SurveyResponseComponent {
39650
39698
  async submit(event) {
39651
39699
  if (this.sending())
39652
39700
  return;
39653
- if (!event && this.form.invalid) {
39701
+ const stepped = this.hasResponseSteps();
39702
+ if (!event && stepped && !this.formComponent?.validate()) {
39654
39703
  this.markInvalid();
39655
39704
  return;
39656
39705
  }
39657
- if (!event && this.hasMissingRequiredMultiSelect()) {
39706
+ if (!event && !stepped && this.form.invalid) {
39658
39707
  this.markInvalid();
39659
39708
  return;
39660
39709
  }
39661
- if (this.needsIdentity() && (this.nameControl.invalid || this.emailControl.invalid)) {
39662
- this.nameControl.markAsTouched();
39663
- this.emailControl.markAsTouched();
39710
+ const currentFields = event?.fields ?? this.formComponent?.Form.getRawValue() ?? this.form.getRawValue();
39711
+ const fields = stepped
39712
+ ? {
39713
+ ...this.responseValues(),
39714
+ ...currentFields,
39715
+ }
39716
+ : currentFields;
39717
+ if (!event && this.hasMissingRequiredMultiSelect(fields)) {
39718
+ this.markInvalid();
39664
39719
  return;
39665
39720
  }
39666
39721
  const cfg = this.typeConfig();
39667
39722
  if (!cfg)
39668
39723
  return;
39724
+ if (this.needsIdentity() && (this.nameControl.invalid || this.emailControl.invalid)) {
39725
+ this.nameControl.markAsTouched();
39726
+ this.emailControl.markAsTouched();
39727
+ return;
39728
+ }
39669
39729
  this.sending.set(true);
39670
39730
  const payload = {
39671
39731
  type: cfg.typeId,
39672
39732
  title: cfg.label,
39673
- fields: event?.fields ?? this.formComponent?.Form.getRawValue() ?? this.form.getRawValue(),
39733
+ fields,
39674
39734
  ...(this.props.inviteToken ? { inviteToken: this.props.inviteToken } : {}),
39675
39735
  ...(this.needsIdentity()
39676
39736
  ? { submitter: { name: this.nameControl.value.trim(), email: this.emailControl.value.trim() } }
@@ -39701,11 +39761,59 @@ class SurveyResponseComponent {
39701
39761
  });
39702
39762
  }
39703
39763
  }
39704
- hasMissingRequiredMultiSelect() {
39764
+ nextResponseStep() {
39765
+ if (!this.formComponent?.validate()) {
39766
+ this.markInvalid();
39767
+ return;
39768
+ }
39769
+ this.responseValues.update(values => ({ ...values, ...this.formComponent?.Form.getRawValue() }));
39770
+ this.responseStep.update(step => Math.min(step + 1, this.responseSections().length - 1));
39771
+ }
39772
+ previousResponseStep() {
39773
+ this.responseValues.update(values => ({ ...values, ...this.formComponent?.Form.getRawValue() }));
39774
+ this.responseStep.update(step => Math.max(step - 1, 0));
39775
+ }
39776
+ responseBackProps() {
39777
+ return {
39778
+ token: 'survey-response-back',
39779
+ text: this.t('previous'),
39780
+ color: 'dark',
39781
+ fill: 'outline',
39782
+ shape: 'round',
39783
+ size: 'default',
39784
+ type: 'button',
39785
+ state: ComponentStates.ENABLED,
39786
+ };
39787
+ }
39788
+ responseNextProps() {
39789
+ return {
39790
+ token: 'survey-response-next',
39791
+ text: this.t('next'),
39792
+ color: 'dark',
39793
+ fill: 'solid',
39794
+ shape: 'round',
39795
+ size: 'default',
39796
+ type: 'button',
39797
+ state: ComponentStates.ENABLED,
39798
+ };
39799
+ }
39800
+ responseSubmitProps() {
39801
+ return {
39802
+ token: 'survey-response-submit',
39803
+ text: this.t('submit'),
39804
+ color: 'dark',
39805
+ fill: 'solid',
39806
+ shape: 'round',
39807
+ size: 'default',
39808
+ type: 'button',
39809
+ state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
39810
+ };
39811
+ }
39812
+ hasMissingRequiredMultiSelect(fields) {
39705
39813
  return this.questions()
39706
39814
  .filter(q => q.type === 'MULTI_SELECT' && q.required)
39707
39815
  .some(q => {
39708
- const value = this.form.get(q.name)?.value;
39816
+ const value = fields ? fields[q.name] : this.form.get(q.name)?.value;
39709
39817
  return !Array.isArray(value) || !value.length;
39710
39818
  });
39711
39819
  }
@@ -39764,13 +39872,38 @@ class SurveyResponseComponent {
39764
39872
  }
39765
39873
 
39766
39874
  @if (formProps(); as props) {
39875
+ @if (hasResponseSteps()) {
39876
+ <nav class="survey-response__steps" [attr.aria-label]="t('stepsLabel')">
39877
+ @for (section of responseSections(); track section.name; let i = $index) {
39878
+ <span
39879
+ class="survey-response__step"
39880
+ [class.survey-response__step--active]="responseStep() === i"
39881
+ [attr.aria-current]="responseStep() === i ? 'step' : null"
39882
+ >
39883
+ {{ i + 1 }}
39884
+ </span>
39885
+ }
39886
+ </nav>
39887
+ }
39767
39888
  <val-form [props]="props" (onSubmit)="submit($event)" (onInvalid)="markInvalid()" />
39889
+ @if (hasResponseSteps()) {
39890
+ <div class="survey-response__actions">
39891
+ @if (responseStep() > 0) {
39892
+ <val-button [props]="responseBackProps()" (onClick)="previousResponseStep()" />
39893
+ }
39894
+ @if (responseStep() < responseSections().length - 1) {
39895
+ <val-button [props]="responseNextProps()" (onClick)="nextResponseStep()" />
39896
+ } @else {
39897
+ <val-button [props]="responseSubmitProps()" (onClick)="submit()" />
39898
+ }
39899
+ </div>
39900
+ }
39768
39901
  }
39769
39902
  </div>
39770
39903
  }
39771
39904
  }
39772
39905
  }
39773
- `, isInline: true, styles: [":host{display:block}.survey-response{display:flex;flex-direction:column;gap:16px}.survey-response__header-image,.survey-response__success-image{display:block;width:100%;max-height:220px;object-fit:contain;border-radius:var(--val-radius-md, 12px)}.survey-response__success{align-items:center;text-align:center}.survey-response__success-image{max-width:320px}.survey-response__success-note{margin:0;color:var(--ion-color-medium, #92949c);font-size:.95rem;font-weight:600}.survey-response__error{margin:0;color:var(--ion-color-danger);font-size:.875rem;font-weight:700}.survey-response__hint{display:block;margin-top:4px;font-size:.8125rem;color:var(--ion-color-medium, #92949c)}.survey-response__loading{min-height:120px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { 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: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onValueChange", "onInvalid", "onSelectChange"] }] }); }
39906
+ `, isInline: true, styles: [":host{display:block}.survey-response{display:flex;flex-direction:column;gap:16px}.survey-response__header-image,.survey-response__success-image{display:block;width:100%;max-height:220px;object-fit:contain;border-radius:var(--val-radius-md, 12px)}.survey-response__success{align-items:center;text-align:center}.survey-response__success-image{max-width:320px}.survey-response__success-note{margin:0;color:var(--ion-color-medium, #92949c);font-size:.95rem;font-weight:600}.survey-response__steps{display:flex;gap:6px}.survey-response__step{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .12));border-radius:999px;color:var(--ion-color-medium, #92949c);font-size:.8125rem;font-weight:700}.survey-response__step--active{border-color:var(--ion-color-primary);color:var(--ion-color-primary)}.survey-response__actions{display:flex;justify-content:flex-end;gap:8px}.survey-response__error{margin:0;color:var(--ion-color-danger);font-size:.875rem;font-weight:700}.survey-response__hint{display:block;margin-top:4px;font-size:.8125rem;color:var(--ion-color-medium, #92949c)}.survey-response__loading{min-height:120px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { 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: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onValueChange", "onInvalid", "onSelectChange"] }] }); }
39774
39907
  }
39775
39908
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyResponseComponent, decorators: [{
39776
39909
  type: Component,
@@ -39841,13 +39974,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
39841
39974
  }
39842
39975
 
39843
39976
  @if (formProps(); as props) {
39977
+ @if (hasResponseSteps()) {
39978
+ <nav class="survey-response__steps" [attr.aria-label]="t('stepsLabel')">
39979
+ @for (section of responseSections(); track section.name; let i = $index) {
39980
+ <span
39981
+ class="survey-response__step"
39982
+ [class.survey-response__step--active]="responseStep() === i"
39983
+ [attr.aria-current]="responseStep() === i ? 'step' : null"
39984
+ >
39985
+ {{ i + 1 }}
39986
+ </span>
39987
+ }
39988
+ </nav>
39989
+ }
39844
39990
  <val-form [props]="props" (onSubmit)="submit($event)" (onInvalid)="markInvalid()" />
39991
+ @if (hasResponseSteps()) {
39992
+ <div class="survey-response__actions">
39993
+ @if (responseStep() > 0) {
39994
+ <val-button [props]="responseBackProps()" (onClick)="previousResponseStep()" />
39995
+ }
39996
+ @if (responseStep() < responseSections().length - 1) {
39997
+ <val-button [props]="responseNextProps()" (onClick)="nextResponseStep()" />
39998
+ } @else {
39999
+ <val-button [props]="responseSubmitProps()" (onClick)="submit()" />
40000
+ }
40001
+ </div>
40002
+ }
39845
40003
  }
39846
40004
  </div>
39847
40005
  }
39848
40006
  }
39849
40007
  }
39850
- `, styles: [":host{display:block}.survey-response{display:flex;flex-direction:column;gap:16px}.survey-response__header-image,.survey-response__success-image{display:block;width:100%;max-height:220px;object-fit:contain;border-radius:var(--val-radius-md, 12px)}.survey-response__success{align-items:center;text-align:center}.survey-response__success-image{max-width:320px}.survey-response__success-note{margin:0;color:var(--ion-color-medium, #92949c);font-size:.95rem;font-weight:600}.survey-response__error{margin:0;color:var(--ion-color-danger);font-size:.875rem;font-weight:700}.survey-response__hint{display:block;margin-top:4px;font-size:.8125rem;color:var(--ion-color-medium, #92949c)}.survey-response__loading{min-height:120px}\n"] }]
40008
+ `, styles: [":host{display:block}.survey-response{display:flex;flex-direction:column;gap:16px}.survey-response__header-image,.survey-response__success-image{display:block;width:100%;max-height:220px;object-fit:contain;border-radius:var(--val-radius-md, 12px)}.survey-response__success{align-items:center;text-align:center}.survey-response__success-image{max-width:320px}.survey-response__success-note{margin:0;color:var(--ion-color-medium, #92949c);font-size:.95rem;font-weight:600}.survey-response__steps{display:flex;gap:6px}.survey-response__step{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .12));border-radius:999px;color:var(--ion-color-medium, #92949c);font-size:.8125rem;font-weight:700}.survey-response__step--active{border-color:var(--ion-color-primary);color:var(--ion-color-primary)}.survey-response__actions{display:flex;justify-content:flex-end;gap:8px}.survey-response__error{margin:0;color:var(--ion-color-danger);font-size:.875rem;font-weight:700}.survey-response__hint{display:block;margin-top:4px;font-size:.8125rem;color:var(--ion-color-medium, #92949c)}.survey-response__loading{min-height:120px}\n"] }]
39851
40009
  }], ctorParameters: () => [], propDecorators: { props: [{
39852
40010
  type: Input
39853
40011
  }], submitted: [{
@@ -39865,6 +40023,8 @@ const FIELD_SCHEMA_EDITOR_I18N = {
39865
40023
  typeLabel: 'Tipo de campo',
39866
40024
  placeholderLabel: 'Texto de ayuda dentro del campo',
39867
40025
  hintLabel: 'Nota bajo el campo',
40026
+ sectionLabel: 'Sección',
40027
+ sectionPlaceholder: 'Ej: Información personal',
39868
40028
  requiredLabel: 'Obligatorio',
39869
40029
  optionsTitle: 'Opciones',
39870
40030
  optionsHint: 'Estas son las alternativas que puede elegir quien responde.',
@@ -39900,6 +40060,8 @@ const FIELD_SCHEMA_EDITOR_I18N = {
39900
40060
  typeLabel: 'Field type',
39901
40061
  placeholderLabel: 'Helper text inside the field',
39902
40062
  hintLabel: 'Note below the field',
40063
+ sectionLabel: 'Section',
40064
+ sectionPlaceholder: 'E.g: Personal information',
39903
40065
  requiredLabel: 'Required',
39904
40066
  optionsTitle: 'Options',
39905
40067
  optionsHint: 'These are the choices the person can pick.',
@@ -39974,6 +40136,7 @@ class FieldSchemaEditorComponent {
39974
40136
  this.labelControl = new FormControl('', { nonNullable: true });
39975
40137
  this.placeholderControl = new FormControl('', { nonNullable: true });
39976
40138
  this.hintControl = new FormControl('', { nonNullable: true });
40139
+ this.sectionControl = new FormControl('', { nonNullable: true });
39977
40140
  this.requiredControl = new FormControl(true, { nonNullable: true });
39978
40141
  this.typeControl = new FormControl('TEXT', { nonNullable: true });
39979
40142
  this.options = signal([]);
@@ -40029,6 +40192,7 @@ class FieldSchemaEditorComponent {
40029
40192
  });
40030
40193
  this.placeholderControl.valueChanges.subscribe(() => this.emitChange());
40031
40194
  this.hintControl.valueChanges.subscribe(() => this.emitChange());
40195
+ this.sectionControl.valueChanges.subscribe(() => this.emitChange());
40032
40196
  this.requiredControl.valueChanges.subscribe(() => this.emitChange());
40033
40197
  }
40034
40198
  ngOnInit() {
@@ -40040,6 +40204,7 @@ class FieldSchemaEditorComponent {
40040
40204
  this.labelControl.setValue(initial.label ?? '');
40041
40205
  this.placeholderControl.setValue(initial.placeholderText ?? '');
40042
40206
  this.hintControl.setValue(initial.hintText ?? '');
40207
+ this.sectionControl.setValue(initial.sectionLabel ?? '');
40043
40208
  this.requiredControl.setValue(!!initial.required);
40044
40209
  this.typeControl.setValue(this.coerceType(initial.type));
40045
40210
  this.options.set(initial.options ?? []);
@@ -40087,6 +40252,7 @@ class FieldSchemaEditorComponent {
40087
40252
  this.labelControl.setValue(value.label ?? '');
40088
40253
  this.placeholderControl.setValue(value.placeholderText ?? '');
40089
40254
  this.hintControl.setValue(value.hintText ?? '');
40255
+ this.sectionControl.setValue(value.sectionLabel ?? '');
40090
40256
  this.requiredControl.setValue(!!value.required);
40091
40257
  this.typeControl.setValue(this.coerceType(value.type));
40092
40258
  this.options.set(value.options ?? []);
@@ -40121,6 +40287,7 @@ class FieldSchemaEditorComponent {
40121
40287
  hintText: (this.hintControl.value || '').trim(),
40122
40288
  type: this.coerceType(this.typeControl.value),
40123
40289
  required: this.requiredControl.value,
40290
+ sectionLabel: (this.sectionControl.value || '').trim(),
40124
40291
  options: this.needsOptions() ? this.options().map((option, index) => ({ ...option, order: index })) : undefined,
40125
40292
  };
40126
40293
  }
@@ -40181,6 +40348,10 @@ class FieldSchemaEditorComponent {
40181
40348
  </val-form-field>
40182
40349
  }
40183
40350
 
40351
+ <val-form-field [label]="t('sectionLabel')">
40352
+ <val-text-input [props]="{ control: sectionControl, placeholder: t('sectionPlaceholder'), state: state() }" />
40353
+ </val-form-field>
40354
+
40184
40355
  <val-toggle-input
40185
40356
  [props]="{
40186
40357
  control: requiredControl,
@@ -40253,6 +40424,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
40253
40424
  </val-form-field>
40254
40425
  }
40255
40426
 
40427
+ <val-form-field [label]="t('sectionLabel')">
40428
+ <val-text-input [props]="{ control: sectionControl, placeholder: t('sectionPlaceholder'), state: state() }" />
40429
+ </val-form-field>
40430
+
40256
40431
  <val-toggle-input
40257
40432
  [props]="{
40258
40433
  control: requiredControl,
@@ -40746,8 +40921,12 @@ const SURVEY_BUILDER_I18N = {
40746
40921
  wizardStepsLabel: 'Pasos para crear la encuesta',
40747
40922
  next: 'Siguiente',
40748
40923
  previous: 'Volver',
40924
+ preview: 'Vista previa',
40925
+ previewTitle: 'Vista previa',
40926
+ closePreview: 'Cerrar',
40749
40927
  reviewTitle: 'Resumen',
40750
40928
  reviewNoSubtitle: 'Sin subtítulo',
40929
+ reviewQuestionCount: '{count} pregunta',
40751
40930
  reviewQuestionsCount: '{count} preguntas',
40752
40931
  reviewHeaderImage: 'Imagen inicial configurada',
40753
40932
  reviewSuccessImage: 'Imagen de cierre configurada',
@@ -40801,8 +40980,12 @@ const SURVEY_BUILDER_I18N = {
40801
40980
  wizardStepsLabel: 'Survey builder steps',
40802
40981
  next: 'Next',
40803
40982
  previous: 'Back',
40983
+ preview: 'Preview',
40984
+ previewTitle: 'Preview',
40985
+ closePreview: 'Close',
40804
40986
  reviewTitle: 'Summary',
40805
40987
  reviewNoSubtitle: 'No subtitle',
40988
+ reviewQuestionCount: '{count} question',
40806
40989
  reviewQuestionsCount: '{count} questions',
40807
40990
  reviewHeaderImage: 'Opening image set',
40808
40991
  reviewSuccessImage: 'Closing image set',
@@ -40810,6 +40993,129 @@ const SURVEY_BUILDER_I18N = {
40810
40993
  },
40811
40994
  };
40812
40995
 
40996
+ class SurveyPreviewModalComponent {
40997
+ constructor() {
40998
+ this.modalTitle = '';
40999
+ this.closeLabel = '';
41000
+ this.title = '';
41001
+ this.subtitle = '';
41002
+ this.questions = [];
41003
+ this.successTitle = '';
41004
+ this.successMessage = '';
41005
+ this.requiredSuffix = '';
41006
+ }
41007
+ sections() {
41008
+ const map = new Map();
41009
+ [...this.questions]
41010
+ .sort((a, b) => a.order - b.order)
41011
+ .forEach(question => {
41012
+ const key = question.sectionId || question.sectionLabel || '';
41013
+ const section = map.get(key) ??
41014
+ {
41015
+ name: question.sectionLabel || '',
41016
+ order: question.sectionOrder ?? 0,
41017
+ questions: [],
41018
+ };
41019
+ section.questions.push(question);
41020
+ map.set(key, section);
41021
+ });
41022
+ return [...map.values()].sort((a, b) => a.order - b.order);
41023
+ }
41024
+ close() {
41025
+ this._modalRef?.dismiss(undefined, 'close');
41026
+ }
41027
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyPreviewModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
41028
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SurveyPreviewModalComponent, isStandalone: true, selector: "val-survey-preview-modal", inputs: { modalTitle: "modalTitle", closeLabel: "closeLabel", title: "title", subtitle: "subtitle", presentation: "presentation", questions: "questions", successTitle: "successTitle", successMessage: "successMessage", requiredSuffix: "requiredSuffix", _modalRef: "_modalRef" }, ngImport: i0, template: `
41029
+ <val-modal-layout [title]="modalTitle" [closeLabel]="closeLabel" (close)="close()">
41030
+ <div class="survey-preview">
41031
+ @if (presentation?.headerImageUrl) {
41032
+ <img class="survey-preview__image" [src]="presentation?.headerImageUrl" [alt]="title" />
41033
+ }
41034
+ <val-display [props]="{ content: title, size: 'small', color: 'dark' }" />
41035
+ @if (subtitle) {
41036
+ <val-title [props]="{ content: subtitle, size: 'large', color: '', bold: false }" />
41037
+ }
41038
+ @for (section of sections(); track section.name) {
41039
+ <section class="survey-preview__section">
41040
+ @if (section.name) {
41041
+ <strong class="survey-preview__section-title">{{ section.name }}</strong>
41042
+ }
41043
+ @for (question of section.questions; track question.name) {
41044
+ <article class="survey-preview__question">
41045
+ <strong>{{ question.label }}</strong>
41046
+ <span>{{ question.typeLabel }}{{ question.required ? requiredSuffix : '' }}</span>
41047
+ </article>
41048
+ }
41049
+ </section>
41050
+ }
41051
+ <section class="survey-preview__success">
41052
+ @if (presentation?.successImageUrl) {
41053
+ <img class="survey-preview__success-image" [src]="presentation?.successImageUrl" [alt]="successTitle" />
41054
+ }
41055
+ <strong>{{ successTitle }}</strong>
41056
+ <span>{{ successMessage }}</span>
41057
+ </section>
41058
+ </div>
41059
+ </val-modal-layout>
41060
+ `, isInline: true, styles: [":host{display:block}.survey-preview{display:flex;flex-direction:column;gap:16px}.survey-preview__image,.survey-preview__success-image{display:block;width:100%;max-height:220px;object-fit:contain;border-radius:var(--val-radius-md, 12px)}.survey-preview__section{display:flex;flex-direction:column;gap:10px}.survey-preview__section-title{color:var(--ion-text-color, #000);font-size:1rem}.survey-preview__question,.survey-preview__success{display:flex;flex-direction:column;gap:4px;padding:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:var(--val-radius-md, 12px);background:var(--ion-card-background, var(--ion-background-color, #fff))}.survey-preview__question span,.survey-preview__success span{color:var(--ion-color-medium, #92949c);font-size:.875rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ModalLayoutComponent, selector: "val-modal-layout", inputs: ["title", "subtitle", "closeLabel", "showClose", "actions", "actionsAlign", "footer", "footerClass"], outputs: ["close", "actionClick"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
41061
+ }
41062
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyPreviewModalComponent, decorators: [{
41063
+ type: Component,
41064
+ args: [{ selector: 'val-survey-preview-modal', standalone: true, imports: [CommonModule, ModalLayoutComponent, DisplayComponent, TitleComponent], template: `
41065
+ <val-modal-layout [title]="modalTitle" [closeLabel]="closeLabel" (close)="close()">
41066
+ <div class="survey-preview">
41067
+ @if (presentation?.headerImageUrl) {
41068
+ <img class="survey-preview__image" [src]="presentation?.headerImageUrl" [alt]="title" />
41069
+ }
41070
+ <val-display [props]="{ content: title, size: 'small', color: 'dark' }" />
41071
+ @if (subtitle) {
41072
+ <val-title [props]="{ content: subtitle, size: 'large', color: '', bold: false }" />
41073
+ }
41074
+ @for (section of sections(); track section.name) {
41075
+ <section class="survey-preview__section">
41076
+ @if (section.name) {
41077
+ <strong class="survey-preview__section-title">{{ section.name }}</strong>
41078
+ }
41079
+ @for (question of section.questions; track question.name) {
41080
+ <article class="survey-preview__question">
41081
+ <strong>{{ question.label }}</strong>
41082
+ <span>{{ question.typeLabel }}{{ question.required ? requiredSuffix : '' }}</span>
41083
+ </article>
41084
+ }
41085
+ </section>
41086
+ }
41087
+ <section class="survey-preview__success">
41088
+ @if (presentation?.successImageUrl) {
41089
+ <img class="survey-preview__success-image" [src]="presentation?.successImageUrl" [alt]="successTitle" />
41090
+ }
41091
+ <strong>{{ successTitle }}</strong>
41092
+ <span>{{ successMessage }}</span>
41093
+ </section>
41094
+ </div>
41095
+ </val-modal-layout>
41096
+ `, styles: [":host{display:block}.survey-preview{display:flex;flex-direction:column;gap:16px}.survey-preview__image,.survey-preview__success-image{display:block;width:100%;max-height:220px;object-fit:contain;border-radius:var(--val-radius-md, 12px)}.survey-preview__section{display:flex;flex-direction:column;gap:10px}.survey-preview__section-title{color:var(--ion-text-color, #000);font-size:1rem}.survey-preview__question,.survey-preview__success{display:flex;flex-direction:column;gap:4px;padding:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:var(--val-radius-md, 12px);background:var(--ion-card-background, var(--ion-background-color, #fff))}.survey-preview__question span,.survey-preview__success span{color:var(--ion-color-medium, #92949c);font-size:.875rem}\n"] }]
41097
+ }], propDecorators: { modalTitle: [{
41098
+ type: Input
41099
+ }], closeLabel: [{
41100
+ type: Input
41101
+ }], title: [{
41102
+ type: Input
41103
+ }], subtitle: [{
41104
+ type: Input
41105
+ }], presentation: [{
41106
+ type: Input
41107
+ }], questions: [{
41108
+ type: Input
41109
+ }], successTitle: [{
41110
+ type: Input
41111
+ }], successMessage: [{
41112
+ type: Input
41113
+ }], requiredSuffix: [{
41114
+ type: Input
41115
+ }], _modalRef: [{
41116
+ type: Input
41117
+ }] } });
41118
+
40813
41119
  const NAMESPACE$1 = 'SurveyBuilder';
40814
41120
  const EDITOR_NAMESPACE = 'FieldSchemaEditor';
40815
41121
  const DEFAULT_TYPE_PREFIX = 'survey';
@@ -40846,6 +41152,7 @@ class SurveyBuilderComponent {
40846
41152
  this.requests = inject(RequestService);
40847
41153
  this.formBuilder = inject(RequestFormBuilderService);
40848
41154
  this.errors = inject(ValtechErrorService);
41155
+ this.modals = inject(ModalService);
40849
41156
  this.titleControl = new FormControl('', { nonNullable: true });
40850
41157
  this.subtitleControl = new FormControl('', { nonNullable: true });
40851
41158
  this.headerImageControl = new FormControl('', { nonNullable: true });
@@ -40877,7 +41184,7 @@ class SurveyBuilderComponent {
40877
41184
  }
40878
41185
  this.successTitleControl.setValue(this.defaultSuccessTitle());
40879
41186
  this.successMessageControl.setValue(this.defaultSuccessMessage());
40880
- addIcons({ cloudUploadOutline, trashOutline });
41187
+ addIcons({ cloudUploadOutline, eyeOutline, trashOutline });
40881
41188
  }
40882
41189
  async ngOnInit() {
40883
41190
  // Sembrado directo: la app ya tenía la encuesta, no hace falta leerla.
@@ -41126,6 +41433,9 @@ class SurveyBuilderComponent {
41126
41433
  label: row.label.trim(),
41127
41434
  type: row.type,
41128
41435
  required: row.required,
41436
+ sectionId: row.sectionLabel ? this.formBuilder.toToken(row.sectionLabel) : undefined,
41437
+ sectionLabel: row.sectionLabel,
41438
+ sectionOrder: this.sectionOrderFor(row.sectionLabel, rows),
41129
41439
  placeholderText: row.placeholderText,
41130
41440
  hintText: row.hintText,
41131
41441
  options: row.options,
@@ -41235,9 +41545,60 @@ class SurveyBuilderComponent {
41235
41545
  hintText: f.hintText ?? '',
41236
41546
  type: f.type,
41237
41547
  required: !!f.required,
41548
+ sectionLabel: f.sectionLabel ?? '',
41549
+ sectionOrder: f.sectionOrder,
41238
41550
  options: f.options,
41239
41551
  };
41240
41552
  }
41553
+ reviewQuestionsCount() {
41554
+ const count = this.questions().length;
41555
+ const key = count === 1 ? 'reviewQuestionCount' : 'reviewQuestionsCount';
41556
+ return this.interpolate(this.t(key), { count });
41557
+ }
41558
+ previewProps() {
41559
+ return {
41560
+ token: 'survey-builder-preview',
41561
+ text: this.t('preview'),
41562
+ color: 'dark',
41563
+ fill: 'outline',
41564
+ shape: 'round',
41565
+ size: 'default',
41566
+ type: 'button',
41567
+ state: ComponentStates.ENABLED,
41568
+ icon: { name: 'eye-outline', slot: 'start' },
41569
+ };
41570
+ }
41571
+ async openPreview() {
41572
+ await this.modals.open({
41573
+ component: SurveyPreviewModalComponent,
41574
+ componentProps: {
41575
+ modalTitle: this.t('previewTitle'),
41576
+ closeLabel: this.t('closePreview'),
41577
+ title: this.titleControl.value.trim() || this.t('untitledQuestion'),
41578
+ subtitle: this.subtitleControl.value.trim(),
41579
+ presentation: this.presentationPayload(),
41580
+ successTitle: this.successTitleControl.value.trim() || this.defaultSuccessTitle(),
41581
+ successMessage: this.successMessageControl.value.trim() || this.defaultSuccessMessage(),
41582
+ requiredSuffix: ` · ${this.t('requiredTag')}`,
41583
+ questions: this.questions().map((question, index) => ({
41584
+ ...question,
41585
+ order: index,
41586
+ name: question.name || this.formBuilder.toToken(question.label),
41587
+ typeLabel: this.typeLabel(question.type),
41588
+ sectionId: question.sectionLabel ? this.formBuilder.toToken(question.sectionLabel) : undefined,
41589
+ sectionOrder: this.sectionOrderFor(question.sectionLabel, this.questions()),
41590
+ })),
41591
+ },
41592
+ size: 'medium',
41593
+ });
41594
+ }
41595
+ sectionOrderFor(sectionLabel, rows) {
41596
+ const label = sectionLabel?.trim();
41597
+ if (!label)
41598
+ return undefined;
41599
+ const labels = [...new Set(rows.map(row => row.sectionLabel?.trim()).filter(Boolean))];
41600
+ return labels.indexOf(label);
41601
+ }
41241
41602
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
41242
41603
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SurveyBuilderComponent, isStandalone: true, selector: "val-survey-builder", inputs: { props: "props" }, outputs: { saved: "saved", draft: "draft" }, ngImport: i0, template: `
41243
41604
  <div class="survey-builder">
@@ -41454,7 +41815,9 @@ class SurveyBuilderComponent {
41454
41815
  <div class="survey-builder__row-head">
41455
41816
  <div class="survey-builder__row-text">
41456
41817
  <strong>{{ row.label || t('untitledQuestion') }}</strong>
41457
- <span>{{ typeLabel(row.type) }}{{ row.required ? ' · ' + t('requiredTag') : '' }}</span>
41818
+ <span>
41819
+ {{ typeLabel(row.type) }}{{ row.required ? ' · ' + t('requiredTag') : '' }}{{ row.sectionLabel ? ' · ' + row.sectionLabel : '' }}
41820
+ </span>
41458
41821
  </div>
41459
41822
  <div class="survey-builder__row-actions">
41460
41823
  <val-button
@@ -41528,11 +41891,12 @@ class SurveyBuilderComponent {
41528
41891
  <span class="survey-builder__questions-label">{{ t('reviewTitle') }}</span>
41529
41892
  <strong>{{ titleControl.value || t('untitledQuestion') }}</strong>
41530
41893
  <span>{{ subtitleControl.value || t('reviewNoSubtitle') }}</span>
41531
- <span>{{ interpolate(t('reviewQuestionsCount'), { count: questions().length }) }}</span>
41894
+ <span>{{ reviewQuestionsCount() }}</span>
41532
41895
  @if (headerImageControl.value) {
41533
41896
  <span>{{ t('reviewHeaderImage') }}</span>
41534
41897
  }
41535
41898
  <span>{{ successImageControl.value ? t('reviewSuccessImage') : t('reviewDefaultClose') }}</span>
41899
+ <val-button [props]="previewProps()" (onClick)="openPreview()" />
41536
41900
  </section>
41537
41901
  }
41538
41902
 
@@ -41555,7 +41919,7 @@ class SurveyBuilderComponent {
41555
41919
  <val-button [props]="saveProps()" (onClick)="save()" />
41556
41920
  }
41557
41921
  </div>
41558
- `, isInline: true, styles: [":host{display:block}.survey-builder{display:flex;flex-direction:column;gap:16px}.survey-builder__questions{display:flex;flex-direction:column;gap:12px}.survey-builder__presentation{display:flex;flex-direction:column;gap:12px;padding-top:4px}.survey-builder__section,.survey-builder__review{display:flex;flex-direction:column;gap:12px}.survey-builder__steps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin-bottom:12px}.survey-builder__step{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:40px;padding:8px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .12));border-radius:8px;background:var(--ion-card-background, var(--ion-background-color, #fff));color:var(--ion-color-medium-shade, #6b6675);font-size:.8125rem;font-weight:700;cursor:pointer}.survey-builder__step span{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:999px;background:var(--ion-color-light, #f4f5f8);color:inherit;font-size:.75rem}.survey-builder__step--active{border-color:var(--ion-color-primary);color:var(--ion-color-primary)}.survey-builder__wizard-actions{display:flex;justify-content:flex-end;gap:8px}.survey-builder__review{padding:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:var(--val-radius-md, 12px);background:var(--ion-card-background, var(--ion-background-color, #fff))}.survey-builder__review strong{color:var(--ion-text-color, #000)}.survey-builder__review span:not(.survey-builder__questions-label){color:var(--ion-color-medium, #92949c);font-size:.875rem}.survey-builder__image-field{display:flex;flex-direction:column;gap:10px}.survey-builder__image-preview{position:relative;width:100%}.survey-builder__image-preview img{display:block;width:100%;aspect-ratio:16 / 9;object-fit:contain;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:8px;background:var(--ion-color-light, #f4f5f8)}.survey-builder__image-clear{position:absolute;top:8px;right:8px;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:0;border-radius:999px;background:#0009;color:#fff;cursor:pointer}.survey-builder__image-upload{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:10px 18px;border:2px dashed var(--ion-color-dark, #2f2c3a);border-radius:20px;color:var(--ion-color-dark, #2f2c3a);font-size:.875rem;font-weight:600;cursor:pointer}.survey-builder__image-upload:hover{border-color:var(--ion-color-primary);color:var(--ion-color-primary)}.survey-builder__image-upload--loading{opacity:.7;cursor:default}.survey-builder__image-upload ion-spinner{width:16px;height:16px}.survey-builder__image-upload input{display:none}.survey-builder__questions-label{font-size:.8125rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--ion-color-medium, #92949c)}.survey-builder__empty{margin:0;font-size:.875rem;color:var(--ion-color-medium, #92949c)}.survey-builder__optional-note{margin:0;color:var(--ion-color-medium, #92949c);font-size:.875rem;line-height:1.35}.survey-builder__row{display:flex;flex-direction:column;gap:12px;padding:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:var(--val-radius-md, 12px);background:var(--ion-card-background, var(--ion-background-color, #fff))}.survey-builder__row-head{display:flex;flex-direction:column;gap:8px}.survey-builder__row-text{display:flex;flex-direction:column;gap:2px;min-width:0}.survey-builder__row-text strong{color:var(--ion-text-color, #000);font-size:.9375rem}.survey-builder__row-text span{color:var(--ion-color-medium, #92949c);font-size:.8125rem}.survey-builder__row-actions{display:flex;gap:4px;flex-wrap:wrap}.survey-builder__row-editor{display:flex;flex-direction:column;gap:12px;padding-top:12px;border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .1))}.survey-builder__error{margin:0;color:var(--ion-color-danger);font-size:.875rem;font-weight:700}@media (min-width: 576px){.survey-builder__row-head{flex-direction:row;align-items:flex-start;justify-content:space-between}.survey-builder__row-text{flex:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: TextInputComponent, selector: "val-text-input", inputs: ["preset", "props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: FormFieldComponent, selector: "val-form-field", inputs: ["label"] }, { kind: "component", type: FieldSchemaEditorComponent, selector: "val-field-schema-editor", inputs: ["props"], outputs: ["save", "changed"] }] }); }
41922
+ `, isInline: true, styles: [":host{display:block}.survey-builder{display:flex;flex-direction:column;gap:16px}.survey-builder__questions{display:flex;flex-direction:column;gap:12px}.survey-builder__presentation{display:flex;flex-direction:column;gap:12px;padding-top:4px}.survey-builder__section,.survey-builder__review{display:flex;flex-direction:column;gap:12px}.survey-builder__steps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin-top:16px;margin-bottom:12px}.survey-builder__step{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:40px;padding:8px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .12));border-radius:8px;background:var(--ion-card-background, var(--ion-background-color, #fff));color:var(--ion-color-medium-shade, #6b6675);font-size:.8125rem;font-weight:700;cursor:pointer}.survey-builder__step span{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:999px;background:var(--ion-color-light, #f4f5f8);color:inherit;font-size:.75rem}.survey-builder__step--active{border-color:var(--ion-color-primary);color:var(--ion-color-primary)}.survey-builder__wizard-actions{display:flex;justify-content:flex-end;gap:8px}.survey-builder__review{padding:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:var(--val-radius-md, 12px);background:var(--ion-card-background, var(--ion-background-color, #fff))}.survey-builder__review strong{color:var(--ion-text-color, #000)}.survey-builder__review span:not(.survey-builder__questions-label){color:var(--ion-color-medium, #92949c);font-size:.875rem}.survey-builder__image-field{display:flex;flex-direction:column;gap:10px}.survey-builder__image-preview{position:relative;width:100%}.survey-builder__image-preview img{display:block;width:100%;aspect-ratio:16 / 9;object-fit:contain;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:8px;background:var(--ion-color-light, #f4f5f8)}.survey-builder__image-clear{position:absolute;top:8px;right:8px;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:0;border-radius:999px;background:#0009;color:#fff;cursor:pointer}.survey-builder__image-upload{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:10px 18px;border:2px dashed var(--ion-color-dark, #2f2c3a);border-radius:20px;color:var(--ion-color-dark, #2f2c3a);font-size:.875rem;font-weight:600;cursor:pointer}.survey-builder__image-upload:hover{border-color:var(--ion-color-primary);color:var(--ion-color-primary)}.survey-builder__image-upload--loading{opacity:.7;cursor:default}.survey-builder__image-upload ion-spinner{width:16px;height:16px}.survey-builder__image-upload input{display:none}.survey-builder__questions-label{font-size:.8125rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--ion-color-medium, #92949c)}.survey-builder__empty{margin:0;font-size:.875rem;color:var(--ion-color-medium, #92949c)}.survey-builder__optional-note{margin:0;color:var(--ion-color-medium, #92949c);font-size:.875rem;line-height:1.35}.survey-builder__row{display:flex;flex-direction:column;gap:12px;padding:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:var(--val-radius-md, 12px);background:var(--ion-card-background, var(--ion-background-color, #fff))}.survey-builder__row-head{display:flex;flex-direction:column;gap:8px}.survey-builder__row-text{display:flex;flex-direction:column;gap:2px;min-width:0}.survey-builder__row-text strong{color:var(--ion-text-color, #000);font-size:.9375rem}.survey-builder__row-text span{color:var(--ion-color-medium, #92949c);font-size:.8125rem}.survey-builder__row-actions{display:flex;gap:4px;flex-wrap:wrap}.survey-builder__row-editor{display:flex;flex-direction:column;gap:12px;padding-top:12px;border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .1))}.survey-builder__error{margin:0;color:var(--ion-color-danger);font-size:.875rem;font-weight:700}@media (min-width: 576px){.survey-builder__row-head{flex-direction:row;align-items:flex-start;justify-content:space-between}.survey-builder__row-text{flex:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: TextInputComponent, selector: "val-text-input", inputs: ["preset", "props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: FormFieldComponent, selector: "val-form-field", inputs: ["label"] }, { kind: "component", type: FieldSchemaEditorComponent, selector: "val-field-schema-editor", inputs: ["props"], outputs: ["save", "changed"] }] }); }
41559
41923
  }
41560
41924
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyBuilderComponent, decorators: [{
41561
41925
  type: Component,
@@ -41783,7 +42147,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
41783
42147
  <div class="survey-builder__row-head">
41784
42148
  <div class="survey-builder__row-text">
41785
42149
  <strong>{{ row.label || t('untitledQuestion') }}</strong>
41786
- <span>{{ typeLabel(row.type) }}{{ row.required ? ' · ' + t('requiredTag') : '' }}</span>
42150
+ <span>
42151
+ {{ typeLabel(row.type) }}{{ row.required ? ' · ' + t('requiredTag') : '' }}{{ row.sectionLabel ? ' · ' + row.sectionLabel : '' }}
42152
+ </span>
41787
42153
  </div>
41788
42154
  <div class="survey-builder__row-actions">
41789
42155
  <val-button
@@ -41857,11 +42223,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
41857
42223
  <span class="survey-builder__questions-label">{{ t('reviewTitle') }}</span>
41858
42224
  <strong>{{ titleControl.value || t('untitledQuestion') }}</strong>
41859
42225
  <span>{{ subtitleControl.value || t('reviewNoSubtitle') }}</span>
41860
- <span>{{ interpolate(t('reviewQuestionsCount'), { count: questions().length }) }}</span>
42226
+ <span>{{ reviewQuestionsCount() }}</span>
41861
42227
  @if (headerImageControl.value) {
41862
42228
  <span>{{ t('reviewHeaderImage') }}</span>
41863
42229
  }
41864
42230
  <span>{{ successImageControl.value ? t('reviewSuccessImage') : t('reviewDefaultClose') }}</span>
42231
+ <val-button [props]="previewProps()" (onClick)="openPreview()" />
41865
42232
  </section>
41866
42233
  }
41867
42234
 
@@ -41884,7 +42251,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
41884
42251
  <val-button [props]="saveProps()" (onClick)="save()" />
41885
42252
  }
41886
42253
  </div>
41887
- `, styles: [":host{display:block}.survey-builder{display:flex;flex-direction:column;gap:16px}.survey-builder__questions{display:flex;flex-direction:column;gap:12px}.survey-builder__presentation{display:flex;flex-direction:column;gap:12px;padding-top:4px}.survey-builder__section,.survey-builder__review{display:flex;flex-direction:column;gap:12px}.survey-builder__steps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin-bottom:12px}.survey-builder__step{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:40px;padding:8px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .12));border-radius:8px;background:var(--ion-card-background, var(--ion-background-color, #fff));color:var(--ion-color-medium-shade, #6b6675);font-size:.8125rem;font-weight:700;cursor:pointer}.survey-builder__step span{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:999px;background:var(--ion-color-light, #f4f5f8);color:inherit;font-size:.75rem}.survey-builder__step--active{border-color:var(--ion-color-primary);color:var(--ion-color-primary)}.survey-builder__wizard-actions{display:flex;justify-content:flex-end;gap:8px}.survey-builder__review{padding:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:var(--val-radius-md, 12px);background:var(--ion-card-background, var(--ion-background-color, #fff))}.survey-builder__review strong{color:var(--ion-text-color, #000)}.survey-builder__review span:not(.survey-builder__questions-label){color:var(--ion-color-medium, #92949c);font-size:.875rem}.survey-builder__image-field{display:flex;flex-direction:column;gap:10px}.survey-builder__image-preview{position:relative;width:100%}.survey-builder__image-preview img{display:block;width:100%;aspect-ratio:16 / 9;object-fit:contain;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:8px;background:var(--ion-color-light, #f4f5f8)}.survey-builder__image-clear{position:absolute;top:8px;right:8px;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:0;border-radius:999px;background:#0009;color:#fff;cursor:pointer}.survey-builder__image-upload{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:10px 18px;border:2px dashed var(--ion-color-dark, #2f2c3a);border-radius:20px;color:var(--ion-color-dark, #2f2c3a);font-size:.875rem;font-weight:600;cursor:pointer}.survey-builder__image-upload:hover{border-color:var(--ion-color-primary);color:var(--ion-color-primary)}.survey-builder__image-upload--loading{opacity:.7;cursor:default}.survey-builder__image-upload ion-spinner{width:16px;height:16px}.survey-builder__image-upload input{display:none}.survey-builder__questions-label{font-size:.8125rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--ion-color-medium, #92949c)}.survey-builder__empty{margin:0;font-size:.875rem;color:var(--ion-color-medium, #92949c)}.survey-builder__optional-note{margin:0;color:var(--ion-color-medium, #92949c);font-size:.875rem;line-height:1.35}.survey-builder__row{display:flex;flex-direction:column;gap:12px;padding:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:var(--val-radius-md, 12px);background:var(--ion-card-background, var(--ion-background-color, #fff))}.survey-builder__row-head{display:flex;flex-direction:column;gap:8px}.survey-builder__row-text{display:flex;flex-direction:column;gap:2px;min-width:0}.survey-builder__row-text strong{color:var(--ion-text-color, #000);font-size:.9375rem}.survey-builder__row-text span{color:var(--ion-color-medium, #92949c);font-size:.8125rem}.survey-builder__row-actions{display:flex;gap:4px;flex-wrap:wrap}.survey-builder__row-editor{display:flex;flex-direction:column;gap:12px;padding-top:12px;border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .1))}.survey-builder__error{margin:0;color:var(--ion-color-danger);font-size:.875rem;font-weight:700}@media (min-width: 576px){.survey-builder__row-head{flex-direction:row;align-items:flex-start;justify-content:space-between}.survey-builder__row-text{flex:1}}\n"] }]
42254
+ `, styles: [":host{display:block}.survey-builder{display:flex;flex-direction:column;gap:16px}.survey-builder__questions{display:flex;flex-direction:column;gap:12px}.survey-builder__presentation{display:flex;flex-direction:column;gap:12px;padding-top:4px}.survey-builder__section,.survey-builder__review{display:flex;flex-direction:column;gap:12px}.survey-builder__steps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin-top:16px;margin-bottom:12px}.survey-builder__step{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:40px;padding:8px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .12));border-radius:8px;background:var(--ion-card-background, var(--ion-background-color, #fff));color:var(--ion-color-medium-shade, #6b6675);font-size:.8125rem;font-weight:700;cursor:pointer}.survey-builder__step span{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:999px;background:var(--ion-color-light, #f4f5f8);color:inherit;font-size:.75rem}.survey-builder__step--active{border-color:var(--ion-color-primary);color:var(--ion-color-primary)}.survey-builder__wizard-actions{display:flex;justify-content:flex-end;gap:8px}.survey-builder__review{padding:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:var(--val-radius-md, 12px);background:var(--ion-card-background, var(--ion-background-color, #fff))}.survey-builder__review strong{color:var(--ion-text-color, #000)}.survey-builder__review span:not(.survey-builder__questions-label){color:var(--ion-color-medium, #92949c);font-size:.875rem}.survey-builder__image-field{display:flex;flex-direction:column;gap:10px}.survey-builder__image-preview{position:relative;width:100%}.survey-builder__image-preview img{display:block;width:100%;aspect-ratio:16 / 9;object-fit:contain;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:8px;background:var(--ion-color-light, #f4f5f8)}.survey-builder__image-clear{position:absolute;top:8px;right:8px;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:0;border-radius:999px;background:#0009;color:#fff;cursor:pointer}.survey-builder__image-upload{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:10px 18px;border:2px dashed var(--ion-color-dark, #2f2c3a);border-radius:20px;color:var(--ion-color-dark, #2f2c3a);font-size:.875rem;font-weight:600;cursor:pointer}.survey-builder__image-upload:hover{border-color:var(--ion-color-primary);color:var(--ion-color-primary)}.survey-builder__image-upload--loading{opacity:.7;cursor:default}.survey-builder__image-upload ion-spinner{width:16px;height:16px}.survey-builder__image-upload input{display:none}.survey-builder__questions-label{font-size:.8125rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--ion-color-medium, #92949c)}.survey-builder__empty{margin:0;font-size:.875rem;color:var(--ion-color-medium, #92949c)}.survey-builder__optional-note{margin:0;color:var(--ion-color-medium, #92949c);font-size:.875rem;line-height:1.35}.survey-builder__row{display:flex;flex-direction:column;gap:12px;padding:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:var(--val-radius-md, 12px);background:var(--ion-card-background, var(--ion-background-color, #fff))}.survey-builder__row-head{display:flex;flex-direction:column;gap:8px}.survey-builder__row-text{display:flex;flex-direction:column;gap:2px;min-width:0}.survey-builder__row-text strong{color:var(--ion-text-color, #000);font-size:.9375rem}.survey-builder__row-text span{color:var(--ion-color-medium, #92949c);font-size:.8125rem}.survey-builder__row-actions{display:flex;gap:4px;flex-wrap:wrap}.survey-builder__row-editor{display:flex;flex-direction:column;gap:12px;padding-top:12px;border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .1))}.survey-builder__error{margin:0;color:var(--ion-color-danger);font-size:.875rem;font-weight:700}@media (min-width: 576px){.survey-builder__row-head{flex-direction:row;align-items:flex-start;justify-content:space-between}.survey-builder__row-text{flex:1}}\n"] }]
41888
42255
  }], ctorParameters: () => [], propDecorators: { props: [{
41889
42256
  type: Input
41890
42257
  }], saved: [{