valtech-components 4.0.1002 → 4.0.1003

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.
@@ -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.1002';
73
+ const VERSION = '4.0.1003';
74
74
 
75
75
  function evaluateValtechAccess(rule, context, features = {}, visitedFeatures = new Set()) {
76
76
  if (rule == null)
@@ -40738,6 +40738,18 @@ const SURVEY_BUILDER_I18N = {
40738
40738
  emptyQuestionLabel: 'Todas las preguntas necesitan texto',
40739
40739
  saveError: 'No pudimos guardar la encuesta. Inténtalo de nuevo en un rato.',
40740
40740
  loadError: 'No pudimos cargar la encuesta para editar.',
40741
+ stepStart: 'Inicio',
40742
+ stepQuestions: 'Preguntas',
40743
+ stepClose: 'Cierre',
40744
+ stepReview: 'Revisar',
40745
+ next: 'Siguiente',
40746
+ previous: 'Volver',
40747
+ reviewTitle: 'Resumen',
40748
+ reviewNoSubtitle: 'Sin subtítulo',
40749
+ reviewQuestionsCount: '{count} preguntas',
40750
+ reviewHeaderImage: 'Imagen inicial configurada',
40751
+ reviewSuccessImage: 'Imagen de cierre configurada',
40752
+ reviewDefaultClose: 'Cierre por defecto',
40741
40753
  },
40742
40754
  en: {
40743
40755
  titleLabel: 'Title',
@@ -40777,6 +40789,18 @@ const SURVEY_BUILDER_I18N = {
40777
40789
  emptyQuestionLabel: 'Every question needs text',
40778
40790
  saveError: "We couldn't save the survey. Please try again in a while.",
40779
40791
  loadError: "We couldn't load the survey to edit.",
40792
+ stepStart: 'Start',
40793
+ stepQuestions: 'Questions',
40794
+ stepClose: 'Close',
40795
+ stepReview: 'Review',
40796
+ next: 'Next',
40797
+ previous: 'Back',
40798
+ reviewTitle: 'Summary',
40799
+ reviewNoSubtitle: 'No subtitle',
40800
+ reviewQuestionsCount: '{count} questions',
40801
+ reviewHeaderImage: 'Opening image set',
40802
+ reviewSuccessImage: 'Closing image set',
40803
+ reviewDefaultClose: 'Default closing screen',
40780
40804
  },
40781
40805
  };
40782
40806
 
@@ -40824,9 +40848,16 @@ class SurveyBuilderComponent {
40824
40848
  this.successImageControl = new FormControl('', { nonNullable: true });
40825
40849
  this.questions = signal([]);
40826
40850
  this.editingId = signal('');
40851
+ this.wizardStep = signal(0);
40827
40852
  this.validationError = signal('');
40828
40853
  this.saving = signal(false);
40829
40854
  this.uploadingImage = signal('');
40855
+ this.wizardSteps = [
40856
+ { id: 'start', labelKey: 'stepStart' },
40857
+ { id: 'questions', labelKey: 'stepQuestions' },
40858
+ { id: 'close', labelKey: 'stepClose' },
40859
+ { id: 'review', labelKey: 'stepReview' },
40860
+ ];
40830
40861
  this.allowedImageTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
40831
40862
  this.maxImageBytes = 10 * 1024 * 1024;
40832
40863
  if (!this.i18n.hasNamespace(NAMESPACE$1)) {
@@ -40871,6 +40902,75 @@ class SurveyBuilderComponent {
40871
40902
  questionTypes() {
40872
40903
  return this.props.questionTypes?.length ? this.props.questionTypes : SURVEY_QUESTION_TYPES;
40873
40904
  }
40905
+ isWizard() {
40906
+ return this.props.layout === 'wizard';
40907
+ }
40908
+ showWizardSection(step) {
40909
+ return this.isWizard() && this.wizardStep() === step;
40910
+ }
40911
+ goToStep(step) {
40912
+ if (!this.isWizard())
40913
+ return;
40914
+ if (step <= this.wizardStep()) {
40915
+ this.validationError.set('');
40916
+ this.wizardStep.set(step);
40917
+ return;
40918
+ }
40919
+ while (this.wizardStep() < step) {
40920
+ if (!this.validateWizardStep(this.wizardStep()))
40921
+ return;
40922
+ this.wizardStep.update(current => current + 1);
40923
+ }
40924
+ }
40925
+ nextStep() {
40926
+ if (!this.validateWizardStep(this.wizardStep()))
40927
+ return;
40928
+ this.wizardStep.update(step => Math.min(step + 1, this.wizardSteps.length - 1));
40929
+ }
40930
+ previousStep() {
40931
+ this.validationError.set('');
40932
+ this.wizardStep.update(step => Math.max(step - 1, 0));
40933
+ }
40934
+ wizardBackProps() {
40935
+ return {
40936
+ token: 'survey-builder-wizard-back',
40937
+ text: this.t('previous'),
40938
+ color: 'dark',
40939
+ fill: 'outline',
40940
+ shape: 'round',
40941
+ size: 'default',
40942
+ type: 'button',
40943
+ state: ComponentStates.ENABLED,
40944
+ };
40945
+ }
40946
+ wizardNextProps() {
40947
+ return {
40948
+ token: 'survey-builder-wizard-next',
40949
+ text: this.t('next'),
40950
+ color: 'dark',
40951
+ fill: 'solid',
40952
+ shape: 'round',
40953
+ size: 'default',
40954
+ type: 'button',
40955
+ state: ComponentStates.ENABLED,
40956
+ };
40957
+ }
40958
+ saveProps() {
40959
+ return {
40960
+ token: 'survey-builder-save',
40961
+ text: this.t('save'),
40962
+ color: 'dark',
40963
+ fill: 'solid',
40964
+ shape: 'round',
40965
+ size: 'large',
40966
+ expand: this.isWizard() ? undefined : 'block',
40967
+ type: 'button',
40968
+ state: this.saving() ? ComponentStates.WORKING : ComponentStates.ENABLED,
40969
+ };
40970
+ }
40971
+ interpolate(template, values) {
40972
+ return Object.entries(values).reduce((text, [key, value]) => text.replaceAll(`{${key}}`, String(value)), template);
40973
+ }
40874
40974
  editorProps(row) {
40875
40975
  return { mode: 'edit', field: row, allowedTypes: this.questionTypes() };
40876
40976
  }
@@ -41006,27 +41106,10 @@ class SurveyBuilderComponent {
41006
41106
  if (this.saving())
41007
41107
  return;
41008
41108
  this.validationError.set('');
41009
- const title = this.titleControl.value.trim();
41010
- if (!title) {
41011
- this.titleControl.markAsTouched();
41012
- this.validationError.set(this.t('noTitle'));
41109
+ if (!this.validateWizardStep(0) || !this.validateWizardStep(1))
41013
41110
  return;
41014
- }
41111
+ const title = this.titleControl.value.trim();
41015
41112
  const rows = this.questions();
41016
- if (!rows.length) {
41017
- this.validationError.set(this.t('noQuestions'));
41018
- return;
41019
- }
41020
- if (rows.some(r => !r.label.trim())) {
41021
- this.validationError.set(this.t('emptyQuestionLabel'));
41022
- return;
41023
- }
41024
- // El editor ya lo valida al cerrar cada pregunta, pero una pregunta abierta
41025
- // (o cargada de una encuesta vieja) puede llegar acá sin opciones.
41026
- if (rows.some(r => FIELD_TYPES_WITH_OPTIONS.includes(r.type) && !r.options?.length)) {
41027
- this.validationError.set(this.t('missingOptions'));
41028
- return;
41029
- }
41030
41113
  const fieldSchema = [];
41031
41114
  rows.forEach((row, index) => {
41032
41115
  const name = this.formBuilder.uniqueFieldName(row.name || row.label, fieldSchema);
@@ -41100,6 +41183,35 @@ class SurveyBuilderComponent {
41100
41183
  };
41101
41184
  return Object.values(presentation).some(Boolean) ? presentation : undefined;
41102
41185
  }
41186
+ validateWizardStep(step) {
41187
+ this.validationError.set('');
41188
+ if (step === 0) {
41189
+ if (!this.titleControl.value.trim()) {
41190
+ this.titleControl.markAsTouched();
41191
+ this.validationError.set(this.t('noTitle'));
41192
+ return false;
41193
+ }
41194
+ return true;
41195
+ }
41196
+ if (step === 1) {
41197
+ const rows = this.questions();
41198
+ if (!rows.length) {
41199
+ this.validationError.set(this.t('noQuestions'));
41200
+ return false;
41201
+ }
41202
+ if (rows.some(r => !r.label.trim())) {
41203
+ this.validationError.set(this.t('emptyQuestionLabel'));
41204
+ return false;
41205
+ }
41206
+ // El editor ya lo valida al cerrar cada pregunta, pero una pregunta abierta
41207
+ // (o cargada de una encuesta vieja) puede llegar acá sin opciones.
41208
+ if (rows.some(r => FIELD_TYPES_WITH_OPTIONS.includes(r.type) && !r.options?.length)) {
41209
+ this.validationError.set(this.t('missingOptions'));
41210
+ return false;
41211
+ }
41212
+ }
41213
+ return true;
41214
+ }
41103
41215
  rowFromField(f) {
41104
41216
  return {
41105
41217
  id: `row-${rowUid++}`,
@@ -41115,15 +41227,81 @@ class SurveyBuilderComponent {
41115
41227
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
41116
41228
  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: `
41117
41229
  <div class="survey-builder">
41118
- <val-form-field [label]="t('titleLabel')">
41119
- <val-text-input [props]="{ control: titleControl, placeholder: t('titlePlaceholder') }" />
41120
- </val-form-field>
41121
- <val-form-field [label]="t('subtitleLabel')">
41122
- <val-text-input [props]="{ control: subtitleControl, placeholder: t('subtitlePlaceholder') }" />
41123
- </val-form-field>
41230
+ @if (isWizard()) {
41231
+ <nav class="survey-builder__steps" aria-label="Survey builder steps">
41232
+ @for (step of wizardSteps; track step.id; let i = $index) {
41233
+ <button
41234
+ type="button"
41235
+ class="survey-builder__step"
41236
+ [class.survey-builder__step--active]="wizardStep() === i"
41237
+ [attr.aria-current]="wizardStep() === i ? 'step' : null"
41238
+ (click)="goToStep(i)"
41239
+ >
41240
+ <span>{{ i + 1 }}</span>
41241
+ {{ t(step.labelKey) }}
41242
+ </button>
41243
+ }
41244
+ </nav>
41245
+ }
41246
+
41247
+ @if (!isWizard()) {
41248
+ <val-form-field [label]="t('titleLabel')">
41249
+ <val-text-input [props]="{ control: titleControl, placeholder: t('titlePlaceholder') }" />
41250
+ </val-form-field>
41251
+ <val-form-field [label]="t('subtitleLabel')">
41252
+ <val-text-input [props]="{ control: subtitleControl, placeholder: t('subtitlePlaceholder') }" />
41253
+ </val-form-field>
41254
+ }
41124
41255
 
41125
- <div class="survey-builder__presentation">
41126
- <span class="survey-builder__questions-label">{{ t('presentationLabel') }}</span>
41256
+ @if (showWizardSection(0)) {
41257
+ <section class="survey-builder__section">
41258
+ <val-form-field [label]="t('titleLabel')">
41259
+ <val-text-input [props]="{ control: titleControl, placeholder: t('titlePlaceholder') }" />
41260
+ </val-form-field>
41261
+ <val-form-field [label]="t('subtitleLabel')">
41262
+ <val-text-input [props]="{ control: subtitleControl, placeholder: t('subtitlePlaceholder') }" />
41263
+ </val-form-field>
41264
+ <span class="survey-builder__questions-label">{{ t('presentationLabel') }}</span>
41265
+ <div class="survey-builder__image-field">
41266
+ <val-form-field [label]="t('headerImageLabel')">
41267
+ <val-text-input [props]="{ control: headerImageControl, placeholder: t('headerImagePlaceholder') }" />
41268
+ </val-form-field>
41269
+ @if (headerImageControl.value) {
41270
+ <div class="survey-builder__image-preview">
41271
+ <img [src]="headerImageControl.value" alt="" />
41272
+ <button
41273
+ type="button"
41274
+ class="survey-builder__image-clear"
41275
+ [attr.aria-label]="t('imageRemove')"
41276
+ (click)="clearPresentationImage('header')"
41277
+ >
41278
+ <ion-icon name="trash-outline" aria-hidden="true" />
41279
+ </button>
41280
+ </div>
41281
+ }
41282
+ @if (props.presentationImageUpload) {
41283
+ <label class="survey-builder__image-upload" [class.survey-builder__image-upload--loading]="uploadingImage() === 'header'">
41284
+ @if (uploadingImage() === 'header') {
41285
+ <ion-spinner name="crescent" />
41286
+ } @else {
41287
+ <ion-icon name="cloud-upload-outline" aria-hidden="true" />
41288
+ }
41289
+ <span>{{ headerImageControl.value ? t('imageChange') : t('imageUpload') }}</span>
41290
+ <input
41291
+ type="file"
41292
+ accept="image/jpeg,image/png,image/webp,image/gif,.jpg,.jpeg,.png,.webp,.gif"
41293
+ [disabled]="uploadingImage() === 'header'"
41294
+ (change)="onPresentationFileSelected('header', $event)"
41295
+ />
41296
+ </label>
41297
+ }
41298
+ </div>
41299
+ </section>
41300
+ }
41301
+
41302
+ @if (!isWizard()) {
41303
+ <div class="survey-builder__presentation">
41304
+ <span class="survey-builder__questions-label">{{ t('presentationLabel') }}</span>
41127
41305
  <div class="survey-builder__image-field">
41128
41306
  <val-form-field [label]="t('headerImageLabel')">
41129
41307
  <val-text-input [props]="{ control: headerImageControl, placeholder: t('headerImagePlaceholder') }" />
@@ -41198,8 +41376,56 @@ class SurveyBuilderComponent {
41198
41376
  </label>
41199
41377
  }
41200
41378
  </div>
41201
- </div>
41379
+ </div>
41380
+ }
41202
41381
 
41382
+ @if (showWizardSection(2)) {
41383
+ <section class="survey-builder__section">
41384
+ <span class="survey-builder__questions-label">{{ t('stepClose') }}</span>
41385
+ <val-form-field [label]="t('successTitleLabel')">
41386
+ <val-text-input [props]="{ control: successTitleControl, placeholder: t('successTitlePlaceholder') }" />
41387
+ </val-form-field>
41388
+ <val-form-field [label]="t('successMessageLabel')">
41389
+ <val-text-input [props]="{ control: successMessageControl, placeholder: t('successMessagePlaceholder') }" />
41390
+ </val-form-field>
41391
+ <div class="survey-builder__image-field">
41392
+ <val-form-field [label]="t('successImageLabel')">
41393
+ <val-text-input [props]="{ control: successImageControl, placeholder: t('successImagePlaceholder') }" />
41394
+ </val-form-field>
41395
+ @if (successImageControl.value) {
41396
+ <div class="survey-builder__image-preview">
41397
+ <img [src]="successImageControl.value" alt="" />
41398
+ <button
41399
+ type="button"
41400
+ class="survey-builder__image-clear"
41401
+ [attr.aria-label]="t('imageRemove')"
41402
+ (click)="clearPresentationImage('success')"
41403
+ >
41404
+ <ion-icon name="trash-outline" aria-hidden="true" />
41405
+ </button>
41406
+ </div>
41407
+ }
41408
+ @if (props.presentationImageUpload) {
41409
+ <label class="survey-builder__image-upload" [class.survey-builder__image-upload--loading]="uploadingImage() === 'success'">
41410
+ @if (uploadingImage() === 'success') {
41411
+ <ion-spinner name="crescent" />
41412
+ } @else {
41413
+ <ion-icon name="cloud-upload-outline" aria-hidden="true" />
41414
+ }
41415
+ <span>{{ successImageControl.value ? t('imageChange') : t('imageUpload') }}</span>
41416
+ <input
41417
+ type="file"
41418
+ accept="image/jpeg,image/png,image/webp,image/gif,.jpg,.jpeg,.png,.webp,.gif"
41419
+ [disabled]="uploadingImage() === 'success'"
41420
+ (change)="onPresentationFileSelected('success', $event)"
41421
+ />
41422
+ </label>
41423
+ }
41424
+ </div>
41425
+ </section>
41426
+ }
41427
+
41428
+ @if (showWizardSection(1)) {
41203
41429
  <div class="survey-builder__questions">
41204
41430
  <span class="survey-builder__questions-label">{{ t('questionsLabel') }}</span>
41205
41431
 
@@ -41279,27 +41505,41 @@ class SurveyBuilderComponent {
41279
41505
  (onClick)="addQuestion()"
41280
41506
  />
41281
41507
  </div>
41508
+ }
41509
+
41510
+ @if (showWizardSection(3)) {
41511
+ <section class="survey-builder__review">
41512
+ <span class="survey-builder__questions-label">{{ t('reviewTitle') }}</span>
41513
+ <strong>{{ titleControl.value || t('untitledQuestion') }}</strong>
41514
+ <span>{{ subtitleControl.value || t('reviewNoSubtitle') }}</span>
41515
+ <span>{{ interpolate(t('reviewQuestionsCount'), { count: questions().length }) }}</span>
41516
+ @if (headerImageControl.value) {
41517
+ <span>{{ t('reviewHeaderImage') }}</span>
41518
+ }
41519
+ <span>{{ successImageControl.value ? t('reviewSuccessImage') : t('reviewDefaultClose') }}</span>
41520
+ </section>
41521
+ }
41282
41522
 
41283
41523
  @if (validationError()) {
41284
41524
  <p class="survey-builder__error">{{ validationError() }}</p>
41285
41525
  }
41286
41526
 
41287
- <val-button
41288
- [props]="{
41289
- token: 'survey-builder-save',
41290
- text: t('save'),
41291
- color: 'dark',
41292
- fill: 'solid',
41293
- shape: 'round',
41294
- size: 'large',
41295
- expand: 'block',
41296
- type: 'button',
41297
- state: saving() ? 'WORKING' : 'ENABLED',
41298
- }"
41299
- (onClick)="save()"
41300
- />
41527
+ @if (isWizard()) {
41528
+ <div class="survey-builder__wizard-actions">
41529
+ @if (wizardStep() > 0) {
41530
+ <val-button [props]="wizardBackProps()" (onClick)="previousStep()" />
41531
+ }
41532
+ @if (wizardStep() < wizardSteps.length - 1) {
41533
+ <val-button [props]="wizardNextProps()" (onClick)="nextStep()" />
41534
+ } @else {
41535
+ <val-button [props]="saveProps()" (onClick)="save()" />
41536
+ }
41537
+ </div>
41538
+ } @else {
41539
+ <val-button [props]="saveProps()" (onClick)="save()" />
41540
+ }
41301
41541
  </div>
41302
- `, 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__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-border-color, rgba(0, 0, 0, .16));border-radius:20px;color:var(--ion-color-medium-shade, #6b6675);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__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"] }] }); }
41542
+ `, 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}.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-border-color, rgba(0, 0, 0, .16));border-radius:20px;color:var(--ion-color-medium-shade, #6b6675);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__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"] }] }); }
41303
41543
  }
41304
41544
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyBuilderComponent, decorators: [{
41305
41545
  type: Component,
@@ -41314,15 +41554,81 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
41314
41554
  FieldSchemaEditorComponent,
41315
41555
  ], template: `
41316
41556
  <div class="survey-builder">
41317
- <val-form-field [label]="t('titleLabel')">
41318
- <val-text-input [props]="{ control: titleControl, placeholder: t('titlePlaceholder') }" />
41319
- </val-form-field>
41320
- <val-form-field [label]="t('subtitleLabel')">
41321
- <val-text-input [props]="{ control: subtitleControl, placeholder: t('subtitlePlaceholder') }" />
41322
- </val-form-field>
41557
+ @if (isWizard()) {
41558
+ <nav class="survey-builder__steps" aria-label="Survey builder steps">
41559
+ @for (step of wizardSteps; track step.id; let i = $index) {
41560
+ <button
41561
+ type="button"
41562
+ class="survey-builder__step"
41563
+ [class.survey-builder__step--active]="wizardStep() === i"
41564
+ [attr.aria-current]="wizardStep() === i ? 'step' : null"
41565
+ (click)="goToStep(i)"
41566
+ >
41567
+ <span>{{ i + 1 }}</span>
41568
+ {{ t(step.labelKey) }}
41569
+ </button>
41570
+ }
41571
+ </nav>
41572
+ }
41573
+
41574
+ @if (!isWizard()) {
41575
+ <val-form-field [label]="t('titleLabel')">
41576
+ <val-text-input [props]="{ control: titleControl, placeholder: t('titlePlaceholder') }" />
41577
+ </val-form-field>
41578
+ <val-form-field [label]="t('subtitleLabel')">
41579
+ <val-text-input [props]="{ control: subtitleControl, placeholder: t('subtitlePlaceholder') }" />
41580
+ </val-form-field>
41581
+ }
41582
+
41583
+ @if (showWizardSection(0)) {
41584
+ <section class="survey-builder__section">
41585
+ <val-form-field [label]="t('titleLabel')">
41586
+ <val-text-input [props]="{ control: titleControl, placeholder: t('titlePlaceholder') }" />
41587
+ </val-form-field>
41588
+ <val-form-field [label]="t('subtitleLabel')">
41589
+ <val-text-input [props]="{ control: subtitleControl, placeholder: t('subtitlePlaceholder') }" />
41590
+ </val-form-field>
41591
+ <span class="survey-builder__questions-label">{{ t('presentationLabel') }}</span>
41592
+ <div class="survey-builder__image-field">
41593
+ <val-form-field [label]="t('headerImageLabel')">
41594
+ <val-text-input [props]="{ control: headerImageControl, placeholder: t('headerImagePlaceholder') }" />
41595
+ </val-form-field>
41596
+ @if (headerImageControl.value) {
41597
+ <div class="survey-builder__image-preview">
41598
+ <img [src]="headerImageControl.value" alt="" />
41599
+ <button
41600
+ type="button"
41601
+ class="survey-builder__image-clear"
41602
+ [attr.aria-label]="t('imageRemove')"
41603
+ (click)="clearPresentationImage('header')"
41604
+ >
41605
+ <ion-icon name="trash-outline" aria-hidden="true" />
41606
+ </button>
41607
+ </div>
41608
+ }
41609
+ @if (props.presentationImageUpload) {
41610
+ <label class="survey-builder__image-upload" [class.survey-builder__image-upload--loading]="uploadingImage() === 'header'">
41611
+ @if (uploadingImage() === 'header') {
41612
+ <ion-spinner name="crescent" />
41613
+ } @else {
41614
+ <ion-icon name="cloud-upload-outline" aria-hidden="true" />
41615
+ }
41616
+ <span>{{ headerImageControl.value ? t('imageChange') : t('imageUpload') }}</span>
41617
+ <input
41618
+ type="file"
41619
+ accept="image/jpeg,image/png,image/webp,image/gif,.jpg,.jpeg,.png,.webp,.gif"
41620
+ [disabled]="uploadingImage() === 'header'"
41621
+ (change)="onPresentationFileSelected('header', $event)"
41622
+ />
41623
+ </label>
41624
+ }
41625
+ </div>
41626
+ </section>
41627
+ }
41323
41628
 
41324
- <div class="survey-builder__presentation">
41325
- <span class="survey-builder__questions-label">{{ t('presentationLabel') }}</span>
41629
+ @if (!isWizard()) {
41630
+ <div class="survey-builder__presentation">
41631
+ <span class="survey-builder__questions-label">{{ t('presentationLabel') }}</span>
41326
41632
  <div class="survey-builder__image-field">
41327
41633
  <val-form-field [label]="t('headerImageLabel')">
41328
41634
  <val-text-input [props]="{ control: headerImageControl, placeholder: t('headerImagePlaceholder') }" />
@@ -41397,8 +41703,56 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
41397
41703
  </label>
41398
41704
  }
41399
41705
  </div>
41400
- </div>
41706
+ </div>
41707
+ }
41708
+
41709
+ @if (showWizardSection(2)) {
41710
+ <section class="survey-builder__section">
41711
+ <span class="survey-builder__questions-label">{{ t('stepClose') }}</span>
41712
+ <val-form-field [label]="t('successTitleLabel')">
41713
+ <val-text-input [props]="{ control: successTitleControl, placeholder: t('successTitlePlaceholder') }" />
41714
+ </val-form-field>
41715
+ <val-form-field [label]="t('successMessageLabel')">
41716
+ <val-text-input [props]="{ control: successMessageControl, placeholder: t('successMessagePlaceholder') }" />
41717
+ </val-form-field>
41718
+ <div class="survey-builder__image-field">
41719
+ <val-form-field [label]="t('successImageLabel')">
41720
+ <val-text-input [props]="{ control: successImageControl, placeholder: t('successImagePlaceholder') }" />
41721
+ </val-form-field>
41722
+ @if (successImageControl.value) {
41723
+ <div class="survey-builder__image-preview">
41724
+ <img [src]="successImageControl.value" alt="" />
41725
+ <button
41726
+ type="button"
41727
+ class="survey-builder__image-clear"
41728
+ [attr.aria-label]="t('imageRemove')"
41729
+ (click)="clearPresentationImage('success')"
41730
+ >
41731
+ <ion-icon name="trash-outline" aria-hidden="true" />
41732
+ </button>
41733
+ </div>
41734
+ }
41735
+ @if (props.presentationImageUpload) {
41736
+ <label class="survey-builder__image-upload" [class.survey-builder__image-upload--loading]="uploadingImage() === 'success'">
41737
+ @if (uploadingImage() === 'success') {
41738
+ <ion-spinner name="crescent" />
41739
+ } @else {
41740
+ <ion-icon name="cloud-upload-outline" aria-hidden="true" />
41741
+ }
41742
+ <span>{{ successImageControl.value ? t('imageChange') : t('imageUpload') }}</span>
41743
+ <input
41744
+ type="file"
41745
+ accept="image/jpeg,image/png,image/webp,image/gif,.jpg,.jpeg,.png,.webp,.gif"
41746
+ [disabled]="uploadingImage() === 'success'"
41747
+ (change)="onPresentationFileSelected('success', $event)"
41748
+ />
41749
+ </label>
41750
+ }
41751
+ </div>
41752
+ </section>
41753
+ }
41401
41754
 
41755
+ @if (showWizardSection(1)) {
41402
41756
  <div class="survey-builder__questions">
41403
41757
  <span class="survey-builder__questions-label">{{ t('questionsLabel') }}</span>
41404
41758
 
@@ -41478,27 +41832,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
41478
41832
  (onClick)="addQuestion()"
41479
41833
  />
41480
41834
  </div>
41835
+ }
41836
+
41837
+ @if (showWizardSection(3)) {
41838
+ <section class="survey-builder__review">
41839
+ <span class="survey-builder__questions-label">{{ t('reviewTitle') }}</span>
41840
+ <strong>{{ titleControl.value || t('untitledQuestion') }}</strong>
41841
+ <span>{{ subtitleControl.value || t('reviewNoSubtitle') }}</span>
41842
+ <span>{{ interpolate(t('reviewQuestionsCount'), { count: questions().length }) }}</span>
41843
+ @if (headerImageControl.value) {
41844
+ <span>{{ t('reviewHeaderImage') }}</span>
41845
+ }
41846
+ <span>{{ successImageControl.value ? t('reviewSuccessImage') : t('reviewDefaultClose') }}</span>
41847
+ </section>
41848
+ }
41481
41849
 
41482
41850
  @if (validationError()) {
41483
41851
  <p class="survey-builder__error">{{ validationError() }}</p>
41484
41852
  }
41485
41853
 
41486
- <val-button
41487
- [props]="{
41488
- token: 'survey-builder-save',
41489
- text: t('save'),
41490
- color: 'dark',
41491
- fill: 'solid',
41492
- shape: 'round',
41493
- size: 'large',
41494
- expand: 'block',
41495
- type: 'button',
41496
- state: saving() ? 'WORKING' : 'ENABLED',
41497
- }"
41498
- (onClick)="save()"
41499
- />
41854
+ @if (isWizard()) {
41855
+ <div class="survey-builder__wizard-actions">
41856
+ @if (wizardStep() > 0) {
41857
+ <val-button [props]="wizardBackProps()" (onClick)="previousStep()" />
41858
+ }
41859
+ @if (wizardStep() < wizardSteps.length - 1) {
41860
+ <val-button [props]="wizardNextProps()" (onClick)="nextStep()" />
41861
+ } @else {
41862
+ <val-button [props]="saveProps()" (onClick)="save()" />
41863
+ }
41864
+ </div>
41865
+ } @else {
41866
+ <val-button [props]="saveProps()" (onClick)="save()" />
41867
+ }
41500
41868
  </div>
41501
- `, 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__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-border-color, rgba(0, 0, 0, .16));border-radius:20px;color:var(--ion-color-medium-shade, #6b6675);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__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"] }]
41869
+ `, 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}.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-border-color, rgba(0, 0, 0, .16));border-radius:20px;color:var(--ion-color-medium-shade, #6b6675);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__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"] }]
41502
41870
  }], ctorParameters: () => [], propDecorators: { props: [{
41503
41871
  type: Input
41504
41872
  }], saved: [{