valtech-components 4.0.981 → 4.0.983

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 (24) hide show
  1. package/esm2022/lib/components/atoms/button/button.component.mjs +3 -1
  2. package/esm2022/lib/components/organisms/field-schema-editor/field-schema-editor.component.mjs +332 -0
  3. package/esm2022/lib/components/organisms/field-schema-editor/field-schema-editor.i18n.mjs +71 -0
  4. package/esm2022/lib/components/organisms/field-schema-editor/types.mjs +2 -0
  5. package/esm2022/lib/components/organisms/survey-builder/survey-builder.component.mjs +269 -131
  6. package/esm2022/lib/components/organisms/survey-builder/survey-builder.i18n.mjs +19 -7
  7. package/esm2022/lib/components/organisms/survey-builder/types.mjs +31 -2
  8. package/esm2022/lib/components/organisms/survey-response/survey-response.component.mjs +294 -26
  9. package/esm2022/lib/components/types.mjs +1 -1
  10. package/esm2022/lib/version.mjs +2 -2
  11. package/esm2022/public-api.mjs +3 -1
  12. package/fesm2022/valtech-components.mjs +1056 -227
  13. package/fesm2022/valtech-components.mjs.map +1 -1
  14. package/lib/components/organisms/article/article.component.d.ts +1 -0
  15. package/lib/components/organisms/field-schema-editor/field-schema-editor.component.d.ts +90 -0
  16. package/lib/components/organisms/field-schema-editor/field-schema-editor.i18n.d.ts +2 -0
  17. package/lib/components/organisms/field-schema-editor/types.d.ts +60 -0
  18. package/lib/components/organisms/survey-builder/survey-builder.component.d.ts +26 -14
  19. package/lib/components/organisms/survey-builder/types.d.ts +23 -5
  20. package/lib/components/organisms/survey-response/survey-response.component.d.ts +42 -1
  21. package/lib/components/types.d.ts +6 -0
  22. package/lib/version.d.ts +1 -1
  23. package/package.json +1 -1
  24. package/public-api.d.ts +2 -0
@@ -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.981';
73
+ const VERSION = '4.0.983';
74
74
 
75
75
  function evaluateValtechAccess(rule, context, features = {}, visitedFeatures = new Set()) {
76
76
  if (rule == null)
@@ -12998,6 +12998,7 @@ class ButtonComponent {
12998
12998
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ButtonComponent, isStandalone: true, selector: "val-button", inputs: { preset: "preset", props: "props" }, outputs: { onClick: "onClick" }, usesOnChanges: true, ngImport: i0, template: `
12999
12999
  <ion-button
13000
13000
  [attr.data-testid]="resolvedProps.token"
13001
+ [attr.aria-label]="resolvedProps.ariaLabel || null"
13001
13002
  [type]="resolvedProps.type"
13002
13003
  [color]="resolvedProps.color"
13003
13004
  [expand]="resolvedProps.expand"
@@ -13027,6 +13028,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
13027
13028
  args: [{ selector: 'val-button', standalone: true, imports: [CommonModule, RouterLink, IonButton, IonIcon, IonSpinner, IonText], template: `
13028
13029
  <ion-button
13029
13030
  [attr.data-testid]="resolvedProps.token"
13031
+ [attr.aria-label]="resolvedProps.ariaLabel || null"
13030
13032
  [type]="resolvedProps.type"
13031
13033
  [color]="resolvedProps.color"
13032
13034
  [expand]="resolvedProps.expand"
@@ -38614,6 +38616,66 @@ const REQUEST_STATUSES = [
38614
38616
  */
38615
38617
  const FIELD_TYPE_EMOJI_RATING = 'EMOJI_RATING';
38616
38618
 
38619
+ /**
38620
+ * Primitivo de campo de formulario: label (estilo val-form) + contenido proyectado.
38621
+ *
38622
+ * Usar cuando necesitás un campo fuera de val-form pero con el mismo estilo:
38623
+ * image-picker, date-picker, selectores custom, chips, etc.
38624
+ *
38625
+ * NOTA: para ion-input / ion-textarea NO usar ng-content projection — el Shadow DOM
38626
+ * de Ionic no responde al grid del host. Usá en cambio un `<div class="pf-field">`
38627
+ * plano en el template con `<p class="pf-label">` + ion-input directo.
38628
+ *
38629
+ * ```html
38630
+ * <val-form-field label="Imagen">
38631
+ * <app-image-picker ... />
38632
+ * </val-form-field>
38633
+ *
38634
+ * <!-- Para ion-input: NO val-form-field, usar div plano -->
38635
+ * <div class="pf-field">
38636
+ * <p class="pf-label">Nombre</p>
38637
+ * <ion-input fill="outline" ... />
38638
+ * </div>
38639
+ * ```
38640
+ *
38641
+ * El label usa el mismo val-title que val-form (size=small, color=dark, bold=false).
38642
+ * El spacing entre campos se controla con --val-form-field-gap (default 0.5rem).
38643
+ * El padding horizontal se hereda vía --val-form-field-padding (default 0).
38644
+ * Setearlo en el contenedor padre para consistencia sin override en cada campo:
38645
+ * `.my-card { --val-form-field-padding: 0 16px; }`
38646
+ */
38647
+ class FormFieldComponent {
38648
+ constructor() {
38649
+ this.label = input('');
38650
+ this.titleProps = computed(() => ({
38651
+ content: this.label(),
38652
+ size: 'small',
38653
+ color: 'dark',
38654
+ bold: false,
38655
+ }));
38656
+ }
38657
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
38658
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FormFieldComponent, isStandalone: true, selector: "val-form-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
38659
+ @if (label()) {
38660
+ <div class="vff-label">
38661
+ <val-title [props]="titleProps()" />
38662
+ </div>
38663
+ }
38664
+ <ng-content />
38665
+ `, isInline: true, styles: [":host{display:grid;grid-template-columns:1fr;width:100%;box-sizing:border-box;margin:var(--val-form-field-gap, .5rem) 0;padding:var(--val-form-field-padding, 0)}.vff-label{margin-bottom:.25rem}.vff-label ::ng-deep p{margin:0}\n"], dependencies: [{ kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
38666
+ }
38667
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FormFieldComponent, decorators: [{
38668
+ type: Component,
38669
+ args: [{ selector: 'val-form-field', standalone: true, imports: [TitleComponent], template: `
38670
+ @if (label()) {
38671
+ <div class="vff-label">
38672
+ <val-title [props]="titleProps()" />
38673
+ </div>
38674
+ }
38675
+ <ng-content />
38676
+ `, styles: [":host{display:grid;grid-template-columns:1fr;width:100%;box-sizing:border-box;margin:var(--val-form-field-gap, .5rem) 0;padding:var(--val-form-field-padding, 0)}.vff-label{margin-bottom:.25rem}.vff-label ::ng-deep p{margin:0}\n"] }]
38677
+ }] });
38678
+
38617
38679
  const SURVEY_RESPONSE_I18N = {
38618
38680
  es: {
38619
38681
  nameLabel: 'Tu nombre',
@@ -38653,7 +38715,9 @@ const SURVEY_RESPONSE_I18N = {
38653
38715
  },
38654
38716
  };
38655
38717
 
38656
- const NAMESPACE$2 = 'SurveyResponse';
38718
+ const NAMESPACE$3 = 'SurveyResponse';
38719
+ /** Tipos cuya respuesta es un booleano, no un texto. */
38720
+ const BOOLEAN_TYPES = ['CHECK', 'TOGGLE'];
38657
38721
  /**
38658
38722
  * val-survey-response
38659
38723
  *
@@ -38683,14 +38747,22 @@ class SurveyResponseComponent {
38683
38747
  this.requests = inject(RequestService);
38684
38748
  this.errors = inject(ValtechErrorService);
38685
38749
  this.emojiType = FIELD_TYPE_EMOJI_RATING;
38750
+ this.selectType = InputType.SELECT;
38686
38751
  this.loading = signal(true);
38687
38752
  this.loadError = signal(false);
38688
38753
  this.sent = signal(false);
38689
38754
  this.sending = signal(false);
38690
38755
  this.typeConfig = signal(null);
38756
+ this.multiSelectError = signal(false);
38691
38757
  this.nameControl = new FormControl('', { nonNullable: true });
38692
38758
  this.emailControl = new FormControl('', { nonNullable: true });
38693
38759
  this.form = new FormGroup({});
38760
+ /**
38761
+ * Una casilla por opción de cada pregunta MULTI_SELECT, indexada
38762
+ * `"<pregunta>::<opcion>"`. Viven fuera del `form` porque el valor que viaja
38763
+ * al backend es UNA lista por pregunta, no N booleanos: se arma al enviar.
38764
+ */
38765
+ this.multiControls = new Map();
38694
38766
  /** Preguntas ordenadas — el template las recorre en este orden. */
38695
38767
  this.questions = computed(() => {
38696
38768
  const cfg = this.typeConfig();
@@ -38715,8 +38787,8 @@ class SurveyResponseComponent {
38715
38787
  this.i18n.lang();
38716
38788
  return { variant: 'error', title: this.t('loginRequiredTitle'), description: this.t('loginRequiredBody') };
38717
38789
  });
38718
- if (!this.i18n.hasNamespace(NAMESPACE$2)) {
38719
- this.i18n.registerDefaults(NAMESPACE$2, SURVEY_RESPONSE_I18N);
38790
+ if (!this.i18n.hasNamespace(NAMESPACE$3)) {
38791
+ this.i18n.registerDefaults(NAMESPACE$3, SURVEY_RESPONSE_I18N);
38720
38792
  }
38721
38793
  }
38722
38794
  async ngOnInit() {
@@ -38735,6 +38807,19 @@ class SurveyResponseComponent {
38735
38807
  buildForm(cfg) {
38736
38808
  const group = {};
38737
38809
  for (const q of [...cfg.fieldSchema].sort((a, b) => a.order - b.order)) {
38810
+ if (q.type === 'MULTI_SELECT') {
38811
+ for (const opt of q.options ?? []) {
38812
+ this.multiControls.set(`${q.name}::${opt.id}`, new FormControl(false, { nonNullable: true }));
38813
+ }
38814
+ continue;
38815
+ }
38816
+ if (BOOLEAN_TYPES.includes(q.type)) {
38817
+ // Sin `required`: un booleano SIEMPRE tiene valor. Exigirlo obligaría a
38818
+ // marcarlo, que es otra cosa que responder — y dejaría el envío
38819
+ // bloqueado sin que se vea por qué.
38820
+ group[q.name] = new FormControl(false, { nonNullable: true });
38821
+ continue;
38822
+ }
38738
38823
  group[q.name] = new FormControl({ value: '', disabled: false }, q.required ? [Validators.required] : []);
38739
38824
  }
38740
38825
  this.form = new FormGroup(group);
@@ -38752,6 +38837,74 @@ class SurveyResponseComponent {
38752
38837
  questionControl(name) {
38753
38838
  return this.form.get(name);
38754
38839
  }
38840
+ booleanControl(name) {
38841
+ return this.form.get(name);
38842
+ }
38843
+ optionControl(name, optionId) {
38844
+ const key = `${name}::${optionId}`;
38845
+ let control = this.multiControls.get(key);
38846
+ if (!control) {
38847
+ control = new FormControl(false, { nonNullable: true });
38848
+ this.multiControls.set(key, control);
38849
+ }
38850
+ return control;
38851
+ }
38852
+ /** Opciones tal como las escribió quien creó la encuesta — no se traducen. */
38853
+ optionsOf(q) {
38854
+ return [...(q.options ?? [])]
38855
+ .map((o, index) => ({ id: o.id, name: o.name, order: o.order ?? index }))
38856
+ .sort((a, b) => a.order - b.order);
38857
+ }
38858
+ /**
38859
+ * `val-radio-input` pide un `InputMetadata` completo (no `Partial`), así que
38860
+ * se arma acá en vez de en el template.
38861
+ */
38862
+ radioProps(q) {
38863
+ return {
38864
+ token: q.name,
38865
+ name: q.name,
38866
+ label: '',
38867
+ hint: '',
38868
+ placeholder: '',
38869
+ type: InputType.RADIO,
38870
+ order: q.order,
38871
+ validators: [],
38872
+ errors: {},
38873
+ state: ComponentStates.ENABLED,
38874
+ control: this.questionControl(q.name),
38875
+ options: this.optionsOf(q),
38876
+ };
38877
+ }
38878
+ placeholderOf(q) {
38879
+ return q.placeholderText || q.placeholder || '';
38880
+ }
38881
+ hintOf(q) {
38882
+ return q.hintText || q.hint || '';
38883
+ }
38884
+ /**
38885
+ * Tipo del `<input>` para las preguntas que se responden escribiendo. Nada de
38886
+ * `parseInputType` de `FormSchemaBuilderService`: ese mapea al enum de
38887
+ * `val-form`, y acá no hay `val-form` de por medio (ver el comentario de
38888
+ * clase). Un tipo desconocido cae a texto, que siempre se puede responder.
38889
+ */
38890
+ inputTypeOf(type) {
38891
+ switch (type) {
38892
+ case 'EMAIL':
38893
+ return 'email';
38894
+ case 'NUMBER':
38895
+ return 'number';
38896
+ case 'PHONE':
38897
+ return 'tel';
38898
+ default:
38899
+ return 'text';
38900
+ }
38901
+ }
38902
+ /** Las opciones marcadas de una pregunta MULTI_SELECT, en el orden del schema. */
38903
+ selectedOptions(q) {
38904
+ return this.optionsOf(q)
38905
+ .filter(opt => this.multiControls.get(`${q.name}::${opt.id}`)?.value)
38906
+ .map(opt => opt.id);
38907
+ }
38755
38908
  questionLabel(q) {
38756
38909
  // Texto del CLIENTE (regla #4): quien creó la encuesta escribió el
38757
38910
  // enunciado tal cual, no se traduce.
@@ -38773,8 +38926,20 @@ class SurveyResponseComponent {
38773
38926
  const cfg = this.typeConfig();
38774
38927
  if (!cfg)
38775
38928
  return;
38929
+ // MULTI_SELECT no vive en el FormGroup, así que `form.invalid` no lo cubre:
38930
+ // una obligatoria sin nada marcado hay que frenarla acá o se envía vacía.
38931
+ const multiRequired = this.questions().filter(q => q.type === 'MULTI_SELECT' && q.required);
38932
+ if (multiRequired.some(q => !this.selectedOptions(q).length)) {
38933
+ this.multiSelectError.set(true);
38934
+ return;
38935
+ }
38936
+ this.multiSelectError.set(false);
38776
38937
  this.sending.set(true);
38777
- const fields = this.form.getRawValue();
38938
+ const fields = { ...this.form.getRawValue() };
38939
+ for (const q of this.questions()) {
38940
+ if (q.type === 'MULTI_SELECT')
38941
+ fields[q.name] = this.selectedOptions(q);
38942
+ }
38778
38943
  const payload = {
38779
38944
  type: cfg.typeId,
38780
38945
  title: cfg.label,
@@ -38797,12 +38962,12 @@ class SurveyResponseComponent {
38797
38962
  this.errors.handle(err, {
38798
38963
  context: 'surveyResponse.submit',
38799
38964
  fallbackKey: 'sendError',
38800
- i18nNamespace: NAMESPACE$2,
38965
+ i18nNamespace: NAMESPACE$3,
38801
38966
  });
38802
38967
  }
38803
38968
  }
38804
38969
  t(key) {
38805
- return this.i18n.t(key, NAMESPACE$2);
38970
+ return this.i18n.t(key, NAMESPACE$3);
38806
38971
  }
38807
38972
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyResponseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
38808
38973
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SurveyResponseComponent, isStandalone: true, selector: "val-survey-response", inputs: { props: "props" }, outputs: { submitted: "submitted" }, ngImport: i0, template: `
@@ -38839,21 +39004,97 @@ class SurveyResponseComponent {
38839
39004
 
38840
39005
  @for (q of questions(); track q.name) {
38841
39006
  <div class="survey-response__question">
38842
- @if (q.type === emojiType) {
38843
- <val-emoji-rating [props]="{ control: questionControl(q.name), label: questionLabel(q) }" />
38844
- } @else {
38845
- <val-textarea-input
38846
- [props]="{
38847
- control: questionControl(q.name),
38848
- label: questionLabel(q),
38849
- hint: q.hint,
38850
- placeholder: q.placeholder,
38851
- }"
38852
- />
39007
+ @switch (q.type) {
39008
+ @case (emojiType) {
39009
+ <val-emoji-rating [props]="{ control: questionControl(q.name), label: questionLabel(q) }" />
39010
+ }
39011
+ @case ('TEXTAREA') {
39012
+ <val-textarea-input
39013
+ [props]="{
39014
+ control: questionControl(q.name),
39015
+ label: questionLabel(q),
39016
+ hint: hintOf(q),
39017
+ placeholder: placeholderOf(q),
39018
+ autoGrow: true,
39019
+ }"
39020
+ />
39021
+ }
39022
+ @case ('SELECT') {
39023
+ <val-form-field [label]="questionLabel(q)">
39024
+ <val-select-input
39025
+ [props]="{
39026
+ control: questionControl(q.name),
39027
+ type: selectType,
39028
+ placeholder: placeholderOf(q),
39029
+ options: optionsOf(q),
39030
+ }"
39031
+ />
39032
+ </val-form-field>
39033
+ @if (hintOf(q)) {
39034
+ <span class="survey-response__hint">{{ hintOf(q) }}</span>
39035
+ }
39036
+ }
39037
+ @case ('RADIO') {
39038
+ <val-form-field [label]="questionLabel(q)">
39039
+ <val-radio-input [props]="radioProps(q)" />
39040
+ </val-form-field>
39041
+ @if (hintOf(q)) {
39042
+ <span class="survey-response__hint">{{ hintOf(q) }}</span>
39043
+ }
39044
+ }
39045
+ @case ('MULTI_SELECT') {
39046
+ <val-form-field [label]="questionLabel(q)">
39047
+ <div class="survey-response__options">
39048
+ @for (opt of optionsOf(q); track opt.id) {
39049
+ <val-check-input
39050
+ [props]="{ control: optionControl(q.name, opt.id), label: opt.name, labelPlacement: 'end' }"
39051
+ />
39052
+ }
39053
+ </div>
39054
+ </val-form-field>
39055
+ @if (hintOf(q)) {
39056
+ <span class="survey-response__hint">{{ hintOf(q) }}</span>
39057
+ }
39058
+ }
39059
+ @case ('CHECK') {
39060
+ <val-check-input
39061
+ [props]="{ control: booleanControl(q.name), label: questionLabel(q), labelPlacement: 'end' }"
39062
+ />
39063
+ }
39064
+ @case ('TOGGLE') {
39065
+ <val-toggle-input
39066
+ [props]="{ control: booleanControl(q.name), label: questionLabel(q), justify: 'space-between' }"
39067
+ />
39068
+ }
39069
+ @case ('DATE') {
39070
+ <val-date-picker
39071
+ [props]="{
39072
+ control: questionControl(q.name),
39073
+ label: questionLabel(q),
39074
+ hint: hintOf(q),
39075
+ placeholder: placeholderOf(q),
39076
+ }"
39077
+ />
39078
+ }
39079
+ @default {
39080
+ <val-text-input
39081
+ [props]="{
39082
+ control: questionControl(q.name),
39083
+ label: questionLabel(q),
39084
+ hint: hintOf(q),
39085
+ placeholder: placeholderOf(q),
39086
+ inputType: inputTypeOf(q.type),
39087
+ }"
39088
+ />
39089
+ }
38853
39090
  }
38854
39091
  </div>
38855
39092
  }
38856
39093
 
39094
+ @if (multiSelectError()) {
39095
+ <p class="survey-response__error">{{ t('required') }}</p>
39096
+ }
39097
+
38857
39098
  <val-button
38858
39099
  [props]="{
38859
39100
  token: 'survey-response-submit',
@@ -38872,7 +39113,7 @@ class SurveyResponseComponent {
38872
39113
  }
38873
39114
  }
38874
39115
  }
38875
- `, isInline: true, styles: [":host{display:block}.survey-response{display:flex;flex-direction:column;gap:16px}.survey-response__question{display:block}.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: TextareaInputComponent, selector: "val-textarea-input", inputs: ["preset", "props"] }, { kind: "component", type: EmojiRatingComponent, selector: "val-emoji-rating", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }] }); }
39116
+ `, isInline: true, styles: [":host{display:block}.survey-response{display:flex;flex-direction:column;gap:16px}.survey-response__question{display:block}.survey-response__options{display:flex;flex-direction:column;gap:4px}.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: TextareaInputComponent, selector: "val-textarea-input", inputs: ["preset", "props"] }, { kind: "component", type: SearchSelectorComponent, selector: "val-select-input", inputs: ["preset", "props"] }, { kind: "component", type: RadioInputComponent, selector: "val-radio-input", inputs: ["props"] }, { kind: "component", type: CheckInputComponent, selector: "val-check-input", inputs: ["preset", "props"] }, { kind: "component", type: ToggleInputComponent, selector: "val-toggle-input", inputs: ["preset", "props"] }, { kind: "component", type: DatePickerComponent, selector: "val-date-picker", inputs: ["props"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldComponent, selector: "val-form-field", inputs: ["label"] }, { kind: "component", type: EmojiRatingComponent, selector: "val-emoji-rating", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }] }); }
38876
39117
  }
38877
39118
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyResponseComponent, decorators: [{
38878
39119
  type: Component,
@@ -38883,6 +39124,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
38883
39124
  TitleComponent,
38884
39125
  TextInputComponent,
38885
39126
  TextareaInputComponent,
39127
+ SearchSelectorComponent,
39128
+ RadioInputComponent,
39129
+ CheckInputComponent,
39130
+ ToggleInputComponent,
39131
+ DatePickerComponent,
39132
+ FormFieldComponent,
38886
39133
  EmojiRatingComponent,
38887
39134
  ButtonComponent,
38888
39135
  EmptyStateComponent,
@@ -38920,21 +39167,97 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
38920
39167
 
38921
39168
  @for (q of questions(); track q.name) {
38922
39169
  <div class="survey-response__question">
38923
- @if (q.type === emojiType) {
38924
- <val-emoji-rating [props]="{ control: questionControl(q.name), label: questionLabel(q) }" />
38925
- } @else {
38926
- <val-textarea-input
38927
- [props]="{
38928
- control: questionControl(q.name),
38929
- label: questionLabel(q),
38930
- hint: q.hint,
38931
- placeholder: q.placeholder,
38932
- }"
38933
- />
39170
+ @switch (q.type) {
39171
+ @case (emojiType) {
39172
+ <val-emoji-rating [props]="{ control: questionControl(q.name), label: questionLabel(q) }" />
39173
+ }
39174
+ @case ('TEXTAREA') {
39175
+ <val-textarea-input
39176
+ [props]="{
39177
+ control: questionControl(q.name),
39178
+ label: questionLabel(q),
39179
+ hint: hintOf(q),
39180
+ placeholder: placeholderOf(q),
39181
+ autoGrow: true,
39182
+ }"
39183
+ />
39184
+ }
39185
+ @case ('SELECT') {
39186
+ <val-form-field [label]="questionLabel(q)">
39187
+ <val-select-input
39188
+ [props]="{
39189
+ control: questionControl(q.name),
39190
+ type: selectType,
39191
+ placeholder: placeholderOf(q),
39192
+ options: optionsOf(q),
39193
+ }"
39194
+ />
39195
+ </val-form-field>
39196
+ @if (hintOf(q)) {
39197
+ <span class="survey-response__hint">{{ hintOf(q) }}</span>
39198
+ }
39199
+ }
39200
+ @case ('RADIO') {
39201
+ <val-form-field [label]="questionLabel(q)">
39202
+ <val-radio-input [props]="radioProps(q)" />
39203
+ </val-form-field>
39204
+ @if (hintOf(q)) {
39205
+ <span class="survey-response__hint">{{ hintOf(q) }}</span>
39206
+ }
39207
+ }
39208
+ @case ('MULTI_SELECT') {
39209
+ <val-form-field [label]="questionLabel(q)">
39210
+ <div class="survey-response__options">
39211
+ @for (opt of optionsOf(q); track opt.id) {
39212
+ <val-check-input
39213
+ [props]="{ control: optionControl(q.name, opt.id), label: opt.name, labelPlacement: 'end' }"
39214
+ />
39215
+ }
39216
+ </div>
39217
+ </val-form-field>
39218
+ @if (hintOf(q)) {
39219
+ <span class="survey-response__hint">{{ hintOf(q) }}</span>
39220
+ }
39221
+ }
39222
+ @case ('CHECK') {
39223
+ <val-check-input
39224
+ [props]="{ control: booleanControl(q.name), label: questionLabel(q), labelPlacement: 'end' }"
39225
+ />
39226
+ }
39227
+ @case ('TOGGLE') {
39228
+ <val-toggle-input
39229
+ [props]="{ control: booleanControl(q.name), label: questionLabel(q), justify: 'space-between' }"
39230
+ />
39231
+ }
39232
+ @case ('DATE') {
39233
+ <val-date-picker
39234
+ [props]="{
39235
+ control: questionControl(q.name),
39236
+ label: questionLabel(q),
39237
+ hint: hintOf(q),
39238
+ placeholder: placeholderOf(q),
39239
+ }"
39240
+ />
39241
+ }
39242
+ @default {
39243
+ <val-text-input
39244
+ [props]="{
39245
+ control: questionControl(q.name),
39246
+ label: questionLabel(q),
39247
+ hint: hintOf(q),
39248
+ placeholder: placeholderOf(q),
39249
+ inputType: inputTypeOf(q.type),
39250
+ }"
39251
+ />
39252
+ }
38934
39253
  }
38935
39254
  </div>
38936
39255
  }
38937
39256
 
39257
+ @if (multiSelectError()) {
39258
+ <p class="survey-response__error">{{ t('required') }}</p>
39259
+ }
39260
+
38938
39261
  <val-button
38939
39262
  [props]="{
38940
39263
  token: 'survey-response-submit',
@@ -38953,7 +39276,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
38953
39276
  }
38954
39277
  }
38955
39278
  }
38956
- `, styles: [":host{display:block}.survey-response{display:flex;flex-direction:column;gap:16px}.survey-response__question{display:block}.survey-response__loading{min-height:120px}\n"] }]
39279
+ `, styles: [":host{display:block}.survey-response{display:flex;flex-direction:column;gap:16px}.survey-response__question{display:block}.survey-response__options{display:flex;flex-direction:column;gap:4px}.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"] }]
38957
39280
  }], ctorParameters: () => [], propDecorators: { props: [{
38958
39281
  type: Input
38959
39282
  }], submitted: [{
@@ -39170,6 +39493,395 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
39170
39493
  args: [{ providedIn: 'root' }]
39171
39494
  }] });
39172
39495
 
39496
+ const FIELD_SCHEMA_EDITOR_I18N = {
39497
+ es: {
39498
+ presetsTitle: 'Empieza desde',
39499
+ labelLabel: 'Texto del campo',
39500
+ labelPlaceholder: 'Escribe lo que verá quien responde',
39501
+ typeLabel: 'Tipo de campo',
39502
+ placeholderLabel: 'Texto de ayuda dentro del campo',
39503
+ hintLabel: 'Nota bajo el campo',
39504
+ requiredLabel: 'Obligatorio',
39505
+ optionsTitle: 'Opciones',
39506
+ optionsHint: 'Estas son las alternativas que puede elegir quien responde.',
39507
+ optionsAdd: 'Agregar opción',
39508
+ optionsList: 'Opciones',
39509
+ optionsPlaceholder: 'Escribe una opción',
39510
+ optionsEmpty: 'Todavía no hay opciones.',
39511
+ optionsRemove: 'Quitar opción',
39512
+ optionsReorder: 'Reordenar opción',
39513
+ errorIncomplete: 'Ponle un texto al campo y elige su tipo.',
39514
+ errorOptionsRequired: 'Este tipo de campo necesita al menos una opción.',
39515
+ typeTEXT: 'Texto corto',
39516
+ typeTEXTAREA: 'Texto largo',
39517
+ typeEMAIL: 'Correo',
39518
+ typePHONE: 'Teléfono',
39519
+ typeNUMBER: 'Número',
39520
+ typeDATE: 'Fecha',
39521
+ typeSELECT: 'Lista desplegable',
39522
+ typeRADIO: 'Una sola opción',
39523
+ typeMULTI_SELECT: 'Varias opciones',
39524
+ typeCHECK: 'Casilla',
39525
+ typeTOGGLE: 'Sí o no',
39526
+ typeCURRENCY: 'Monto',
39527
+ typeATTACHMENT: 'Archivo adjunto',
39528
+ typeFILE: 'Archivo',
39529
+ typeEMOJI_RATING: 'Calificación con emoji',
39530
+ },
39531
+ en: {
39532
+ presetsTitle: 'Start from',
39533
+ labelLabel: 'Field text',
39534
+ labelPlaceholder: 'Write what the person will see',
39535
+ typeLabel: 'Field type',
39536
+ placeholderLabel: 'Helper text inside the field',
39537
+ hintLabel: 'Note below the field',
39538
+ requiredLabel: 'Required',
39539
+ optionsTitle: 'Options',
39540
+ optionsHint: 'These are the choices the person can pick.',
39541
+ optionsAdd: 'Add option',
39542
+ optionsList: 'Options',
39543
+ optionsPlaceholder: 'Write an option',
39544
+ optionsEmpty: 'No options yet.',
39545
+ optionsRemove: 'Remove option',
39546
+ optionsReorder: 'Reorder option',
39547
+ errorIncomplete: 'Give the field a text and pick its type.',
39548
+ errorOptionsRequired: 'This field type needs at least one option.',
39549
+ typeTEXT: 'Short text',
39550
+ typeTEXTAREA: 'Long text',
39551
+ typeEMAIL: 'Email',
39552
+ typePHONE: 'Phone',
39553
+ typeNUMBER: 'Number',
39554
+ typeDATE: 'Date',
39555
+ typeSELECT: 'Dropdown',
39556
+ typeRADIO: 'Single choice',
39557
+ typeMULTI_SELECT: 'Multiple choice',
39558
+ typeCHECK: 'Checkbox',
39559
+ typeTOGGLE: 'Yes or no',
39560
+ typeCURRENCY: 'Amount',
39561
+ typeATTACHMENT: 'Attachment',
39562
+ typeFILE: 'File',
39563
+ typeEMOJI_RATING: 'Emoji rating',
39564
+ },
39565
+ };
39566
+
39567
+ const NAMESPACE$2 = 'FieldSchemaEditor';
39568
+ /**
39569
+ * Tipos cuyo valor sale de una lista cerrada que define quien arma el
39570
+ * formulario — sin al menos una opción, el campo no se puede responder.
39571
+ * Espejo de `fieldRequiresOptions` que vivía duplicado en bingo.
39572
+ */
39573
+ const FIELD_TYPES_WITH_OPTIONS = ['SELECT', 'RADIO', 'MULTI_SELECT'];
39574
+ let fieldUid = 0;
39575
+ /**
39576
+ * val-field-schema-editor
39577
+ *
39578
+ * Edita UN campo de un formulario dinámico: texto, tipo, placeholder, nota,
39579
+ * obligatoriedad y opciones. Es la pieza que faltaba en la lib — hasta ahora
39580
+ * el único editor de campos con este nivel vivía dentro de bingo
39581
+ * (`checkout-field-modal.component.ts`), invisible para el resto del factory,
39582
+ * y `val-survey-builder` había terminado con una versión pobre de lo mismo
39583
+ * (dos tipos fijos, sin placeholder ni opciones).
39584
+ *
39585
+ * Es agnóstico de dominio a propósito:
39586
+ * - el **catálogo de tipos** (`allowedTypes`) lo decide el consumer, porque es
39587
+ * él quien sabe qué sabe renderizar su formulario;
39588
+ * - los **presets** los pasa el consumer, porque "correo del comprador" es de
39589
+ * checkout y no significa nada en una encuesta.
39590
+ *
39591
+ * No trae cáscara de modal: el consumer lo envuelve en `val-modal-layout` (o
39592
+ * en lo que quiera) y dispara `submit()` desde su propio botón, igual que hace
39593
+ * `survey-create-modal` con `val-survey-builder`.
39594
+ */
39595
+ class FieldSchemaEditorComponent {
39596
+ constructor() {
39597
+ this.props = { allowedTypes: ['TEXT'] };
39598
+ this.save = new EventEmitter();
39599
+ this.i18n = inject(I18nService);
39600
+ this.formSchemas = inject(FormSchemaBuilderService);
39601
+ this.labelControl = new FormControl('', { nonNullable: true });
39602
+ this.placeholderControl = new FormControl('', { nonNullable: true });
39603
+ this.hintControl = new FormControl('', { nonNullable: true });
39604
+ this.requiredControl = new FormControl(true, { nonNullable: true });
39605
+ this.typeControl = new FormControl('TEXT', { nonNullable: true });
39606
+ this.options = signal([]);
39607
+ this.validationError = signal('');
39608
+ this.selectedPresetId = signal('');
39609
+ /** Se refresca con cada cambio de tipo — `needsOptions` no puede leer el control directo. */
39610
+ this.currentType = signal('TEXT');
39611
+ /** Una vez que se toca el token a mano (o se edita un campo ya guardado) deja de derivarse del label. */
39612
+ this.nameLocked = false;
39613
+ this.name = '';
39614
+ this.fieldId = '';
39615
+ this.typeSelectProps = computed(() => {
39616
+ this.i18n.lang();
39617
+ return {
39618
+ control: this.typeControl,
39619
+ type: InputType.SELECT,
39620
+ state: this.state(),
39621
+ placeholder: this.t('typeLabel'),
39622
+ options: this.allowedTypes().map((type, order) => ({ id: type, name: this.typeLabel(type), order })),
39623
+ };
39624
+ });
39625
+ this.optionsEditorProps = computed(() => {
39626
+ this.i18n.lang();
39627
+ return {
39628
+ title: this.t('optionsTitle'),
39629
+ hint: this.t('optionsHint'),
39630
+ addLabel: this.t('optionsAdd'),
39631
+ optionsLabel: this.t('optionsList'),
39632
+ placeholder: this.t('optionsPlaceholder'),
39633
+ emptyText: this.t('optionsEmpty'),
39634
+ removeOptionLabel: this.t('optionsRemove'),
39635
+ reorderOptionLabel: this.t('optionsReorder'),
39636
+ state: this.state(),
39637
+ options: this.options(),
39638
+ };
39639
+ });
39640
+ if (!this.i18n.hasNamespace(NAMESPACE$2)) {
39641
+ this.i18n.registerDefaults(NAMESPACE$2, FIELD_SCHEMA_EDITOR_I18N);
39642
+ }
39643
+ this.typeControl.valueChanges.subscribe(value => this.currentType.set(value || 'TEXT'));
39644
+ this.labelControl.valueChanges.subscribe(value => {
39645
+ if (!this.nameLocked)
39646
+ this.name = this.formSchemas.toToken(value || '');
39647
+ });
39648
+ }
39649
+ ngOnInit() {
39650
+ const initial = this.props.field;
39651
+ if (initial) {
39652
+ this.fieldId = initial.id;
39653
+ this.nameLocked = true;
39654
+ this.name = initial.name;
39655
+ this.labelControl.setValue(initial.label ?? '');
39656
+ this.placeholderControl.setValue(initial.placeholderText ?? '');
39657
+ this.hintControl.setValue(initial.hintText ?? '');
39658
+ this.requiredControl.setValue(!!initial.required);
39659
+ this.typeControl.setValue(this.coerceType(initial.type));
39660
+ this.options.set(initial.options ?? []);
39661
+ return;
39662
+ }
39663
+ this.fieldId = `field-${++fieldUid}`;
39664
+ this.typeControl.setValue(this.coerceType(this.typeControl.value));
39665
+ const first = this.props.presets?.[0];
39666
+ if (first)
39667
+ this.selectPreset(first);
39668
+ }
39669
+ t(key) {
39670
+ this.i18n.lang();
39671
+ return this.i18n.t(key, NAMESPACE$2);
39672
+ }
39673
+ state() {
39674
+ return this.props.state ?? ComponentStates.ENABLED;
39675
+ }
39676
+ showPresets() {
39677
+ return this.props.mode !== 'edit' && !!this.props.presets?.length;
39678
+ }
39679
+ needsOptions() {
39680
+ return FIELD_TYPES_WITH_OPTIONS.includes(this.currentType());
39681
+ }
39682
+ typeLabel(type) {
39683
+ return this.props.typeLabels?.[type] || this.t(`type${type}`);
39684
+ }
39685
+ selectPreset(preset) {
39686
+ this.selectedPresetId.set(preset.id);
39687
+ const value = preset.field;
39688
+ // El preset fija el token: "correo del comprador" tiene que llamarse
39689
+ // `email` aunque después le cambien el texto visible.
39690
+ this.nameLocked = !!value.name;
39691
+ this.name = value.name || this.formSchemas.toToken(value.label || '');
39692
+ this.labelControl.setValue(value.label ?? '');
39693
+ this.placeholderControl.setValue(value.placeholderText ?? '');
39694
+ this.hintControl.setValue(value.hintText ?? '');
39695
+ this.requiredControl.setValue(!!value.required);
39696
+ this.typeControl.setValue(this.coerceType(value.type));
39697
+ this.options.set(value.options ?? []);
39698
+ }
39699
+ /**
39700
+ * Valida y emite. Público a propósito: lo llama el consumer desde el botón
39701
+ * de su propia cáscara (modal, página o acordeón).
39702
+ */
39703
+ submit() {
39704
+ const field = this.value();
39705
+ if (!field.label || !field.name || !field.type) {
39706
+ this.validationError.set(this.t('errorIncomplete'));
39707
+ return false;
39708
+ }
39709
+ if (this.needsOptions() && !(field.options?.length ?? 0)) {
39710
+ this.validationError.set(this.t('errorOptionsRequired'));
39711
+ return false;
39712
+ }
39713
+ this.validationError.set('');
39714
+ this.save.emit(field);
39715
+ return true;
39716
+ }
39717
+ /** El campo tal como está en pantalla, sin validar. */
39718
+ value() {
39719
+ const label = (this.labelControl.value || '').trim();
39720
+ return {
39721
+ id: this.fieldId,
39722
+ name: this.formSchemas.toToken(this.name || label),
39723
+ label,
39724
+ placeholderText: (this.placeholderControl.value || '').trim(),
39725
+ hintText: (this.hintControl.value || '').trim(),
39726
+ type: this.coerceType(this.typeControl.value),
39727
+ required: this.requiredControl.value,
39728
+ options: this.needsOptions() ? this.options().map((option, index) => ({ ...option, order: index })) : undefined,
39729
+ };
39730
+ }
39731
+ allowedTypes() {
39732
+ return this.props.allowedTypes?.length ? this.props.allowedTypes : ['TEXT'];
39733
+ }
39734
+ /**
39735
+ * Un tipo fuera del catálogo del consumer cae al primero permitido. Pasa al
39736
+ * editar un campo guardado con un tipo que la app ya no ofrece (bingo lo
39737
+ * hacía con ATTACHMENT cuando el medio de pago no lo admite): sin esto, el
39738
+ * select queda en un valor que no está en su lista y se ve vacío.
39739
+ */
39740
+ coerceType(type) {
39741
+ const allowed = this.allowedTypes();
39742
+ return allowed.includes(type) ? type : allowed[0];
39743
+ }
39744
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FieldSchemaEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
39745
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FieldSchemaEditorComponent, isStandalone: true, selector: "val-field-schema-editor", inputs: { props: "props" }, outputs: { save: "save" }, ngImport: i0, template: `
39746
+ <div class="field-schema-editor">
39747
+ @if (showPresets()) {
39748
+ <section class="field-schema-editor__presets">
39749
+ <span class="field-schema-editor__section-label">{{ props.presetsTitle || t('presetsTitle') }}</span>
39750
+ <div class="field-schema-editor__preset-grid">
39751
+ @for (preset of props.presets; track preset.id) {
39752
+ <button
39753
+ type="button"
39754
+ class="field-schema-editor__preset"
39755
+ [class.field-schema-editor__preset--selected]="selectedPresetId() === preset.id"
39756
+ (click)="selectPreset(preset)"
39757
+ >
39758
+ <strong>{{ preset.label }}</strong>
39759
+ @if (preset.description) {
39760
+ <span>{{ preset.description }}</span>
39761
+ }
39762
+ </button>
39763
+ }
39764
+ </div>
39765
+ </section>
39766
+ }
39767
+
39768
+ <val-form-field [label]="t('labelLabel')">
39769
+ <val-text-input [props]="{ control: labelControl, placeholder: t('labelPlaceholder'), state: state() }" />
39770
+ </val-form-field>
39771
+
39772
+ <val-form-field [label]="t('typeLabel')">
39773
+ <val-select-input [props]="typeSelectProps()" />
39774
+ </val-form-field>
39775
+
39776
+ @if (props.showPlaceholder !== false) {
39777
+ <val-form-field [label]="t('placeholderLabel')">
39778
+ <val-textarea-input [props]="{ control: placeholderControl, autoGrow: true, state: state() }" />
39779
+ </val-form-field>
39780
+ }
39781
+
39782
+ @if (props.showHint !== false) {
39783
+ <val-form-field [label]="t('hintLabel')">
39784
+ <val-textarea-input [props]="{ control: hintControl, autoGrow: true, state: state() }" />
39785
+ </val-form-field>
39786
+ }
39787
+
39788
+ <val-toggle-input
39789
+ [props]="{
39790
+ control: requiredControl,
39791
+ label: t('requiredLabel'),
39792
+ justify: 'space-between',
39793
+ state: state(),
39794
+ }"
39795
+ />
39796
+
39797
+ @if (needsOptions()) {
39798
+ <val-field-options-editor [props]="optionsEditorProps()" (optionsChange)="options.set($event)" />
39799
+ }
39800
+
39801
+ @if (validationError()) {
39802
+ <p class="field-schema-editor__error">{{ validationError() }}</p>
39803
+ }
39804
+ </div>
39805
+ `, isInline: true, styles: [":host{display:block}.field-schema-editor{display:flex;flex-direction:column;gap:12px}.field-schema-editor__presets{display:flex;flex-direction:column;gap:8px}.field-schema-editor__section-label{font-size:.8125rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--ion-color-medium, #92949c)}.field-schema-editor__preset-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px}.field-schema-editor__preset{min-height:80px;padding:12px;border:1px solid var(--val-border-color, 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));color:var(--ion-text-color);text-align:left;cursor:pointer}.field-schema-editor__preset strong,.field-schema-editor__preset span{display:block}.field-schema-editor__preset span{margin-top:4px;font-size:.8125rem;line-height:1.3;color:var(--ion-color-medium, #92949c)}.field-schema-editor__preset--selected{border-color:var(--ion-color-primary);box-shadow:0 0 0 2px rgba(var(--ion-color-primary-rgb, 255, 0, 178),.14)}.field-schema-editor__error{margin:0;color:var(--ion-color-danger);font-size:.875rem;font-weight:700}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FormFieldComponent, selector: "val-form-field", inputs: ["label"] }, { kind: "component", type: TextInputComponent, selector: "val-text-input", inputs: ["preset", "props"] }, { kind: "component", type: TextareaInputComponent, selector: "val-textarea-input", inputs: ["preset", "props"] }, { kind: "component", type: SearchSelectorComponent, selector: "val-select-input", inputs: ["preset", "props"] }, { kind: "component", type: ToggleInputComponent, selector: "val-toggle-input", inputs: ["preset", "props"] }, { kind: "component", type: FieldOptionsEditorComponent, selector: "val-field-options-editor", inputs: ["props"], outputs: ["optionsChange"] }] }); }
39806
+ }
39807
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FieldSchemaEditorComponent, decorators: [{
39808
+ type: Component,
39809
+ args: [{ selector: 'val-field-schema-editor', standalone: true, imports: [
39810
+ CommonModule,
39811
+ ReactiveFormsModule,
39812
+ FormFieldComponent,
39813
+ TextInputComponent,
39814
+ TextareaInputComponent,
39815
+ SearchSelectorComponent,
39816
+ ToggleInputComponent,
39817
+ FieldOptionsEditorComponent,
39818
+ ], template: `
39819
+ <div class="field-schema-editor">
39820
+ @if (showPresets()) {
39821
+ <section class="field-schema-editor__presets">
39822
+ <span class="field-schema-editor__section-label">{{ props.presetsTitle || t('presetsTitle') }}</span>
39823
+ <div class="field-schema-editor__preset-grid">
39824
+ @for (preset of props.presets; track preset.id) {
39825
+ <button
39826
+ type="button"
39827
+ class="field-schema-editor__preset"
39828
+ [class.field-schema-editor__preset--selected]="selectedPresetId() === preset.id"
39829
+ (click)="selectPreset(preset)"
39830
+ >
39831
+ <strong>{{ preset.label }}</strong>
39832
+ @if (preset.description) {
39833
+ <span>{{ preset.description }}</span>
39834
+ }
39835
+ </button>
39836
+ }
39837
+ </div>
39838
+ </section>
39839
+ }
39840
+
39841
+ <val-form-field [label]="t('labelLabel')">
39842
+ <val-text-input [props]="{ control: labelControl, placeholder: t('labelPlaceholder'), state: state() }" />
39843
+ </val-form-field>
39844
+
39845
+ <val-form-field [label]="t('typeLabel')">
39846
+ <val-select-input [props]="typeSelectProps()" />
39847
+ </val-form-field>
39848
+
39849
+ @if (props.showPlaceholder !== false) {
39850
+ <val-form-field [label]="t('placeholderLabel')">
39851
+ <val-textarea-input [props]="{ control: placeholderControl, autoGrow: true, state: state() }" />
39852
+ </val-form-field>
39853
+ }
39854
+
39855
+ @if (props.showHint !== false) {
39856
+ <val-form-field [label]="t('hintLabel')">
39857
+ <val-textarea-input [props]="{ control: hintControl, autoGrow: true, state: state() }" />
39858
+ </val-form-field>
39859
+ }
39860
+
39861
+ <val-toggle-input
39862
+ [props]="{
39863
+ control: requiredControl,
39864
+ label: t('requiredLabel'),
39865
+ justify: 'space-between',
39866
+ state: state(),
39867
+ }"
39868
+ />
39869
+
39870
+ @if (needsOptions()) {
39871
+ <val-field-options-editor [props]="optionsEditorProps()" (optionsChange)="options.set($event)" />
39872
+ }
39873
+
39874
+ @if (validationError()) {
39875
+ <p class="field-schema-editor__error">{{ validationError() }}</p>
39876
+ }
39877
+ </div>
39878
+ `, styles: [":host{display:block}.field-schema-editor{display:flex;flex-direction:column;gap:12px}.field-schema-editor__presets{display:flex;flex-direction:column;gap:8px}.field-schema-editor__section-label{font-size:.8125rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--ion-color-medium, #92949c)}.field-schema-editor__preset-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px}.field-schema-editor__preset{min-height:80px;padding:12px;border:1px solid var(--val-border-color, 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));color:var(--ion-text-color);text-align:left;cursor:pointer}.field-schema-editor__preset strong,.field-schema-editor__preset span{display:block}.field-schema-editor__preset span{margin-top:4px;font-size:.8125rem;line-height:1.3;color:var(--ion-color-medium, #92949c)}.field-schema-editor__preset--selected{border-color:var(--ion-color-primary);box-shadow:0 0 0 2px rgba(var(--ion-color-primary-rgb, 255, 0, 178),.14)}.field-schema-editor__error{margin:0;color:var(--ion-color-danger);font-size:.875rem;font-weight:700}\n"] }]
39879
+ }], ctorParameters: () => [], propDecorators: { props: [{
39880
+ type: Input
39881
+ }], save: [{
39882
+ type: Output
39883
+ }] } });
39884
+
39173
39885
  class RequestFormBuilderService extends FormSchemaBuilderService {
39174
39886
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestFormBuilderService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
39175
39887
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestFormBuilderService, providedIn: 'root' }); }
@@ -39179,6 +39891,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
39179
39891
  args: [{ providedIn: 'root' }]
39180
39892
  }] });
39181
39893
 
39894
+ /**
39895
+ * Tipos de pregunta que ofrece una encuesta.
39896
+ *
39897
+ * El corte no es estético: **todo tipo que esta lista ofrezca, `val-survey-response`
39898
+ * tiene que saber renderizarlo**, o la encuesta se guarda bien y se responde mal
39899
+ * (un rating dibujado como caja de texto). Ampliar acá obliga a ampliar el
39900
+ * renderer en la misma entrega.
39901
+ *
39902
+ * Los seis primeros son de respuesta cerrada — `closedAnswerTypes` en
39903
+ * `backend/go/services/request/aggregate.go` los cuenta por valor en el
39904
+ * dashboard. Los seis últimos son abiertos: el dashboard lista sus respuestas.
39905
+ *
39906
+ * `FILE`/`ATTACHMENT` quedan deliberadamente fuera: una encuesta es pública y
39907
+ * anónima por default, y aceptar subidas ahí es superficie de abuso sin un caso
39908
+ * de uso que lo pida.
39909
+ */
39910
+ const SURVEY_QUESTION_TYPES = [
39911
+ 'EMOJI_RATING',
39912
+ 'SELECT',
39913
+ 'RADIO',
39914
+ 'MULTI_SELECT',
39915
+ 'CHECK',
39916
+ 'TOGGLE',
39917
+ 'TEXT',
39918
+ 'TEXTAREA',
39919
+ 'NUMBER',
39920
+ 'DATE',
39921
+ 'EMAIL',
39922
+ 'PHONE',
39923
+ ];
39924
+
39182
39925
  const SURVEY_BUILDER_I18N = {
39183
39926
  es: {
39184
39927
  titleLabel: 'Título',
@@ -39187,10 +39930,16 @@ const SURVEY_BUILDER_I18N = {
39187
39930
  subtitlePlaceholder: 'Ej: Ayúdanos a mejorar respondiendo estas preguntas',
39188
39931
  questionsLabel: 'Preguntas',
39189
39932
  questionLabelPlaceholder: 'Escribe la pregunta',
39190
- typeEmoji: 'Emoji',
39191
- typeText: 'Texto libre',
39192
39933
  addQuestion: 'Agregar pregunta',
39193
- removeQuestion: 'Quitar',
39934
+ removeQuestion: 'Quitar pregunta',
39935
+ editQuestion: 'Editar pregunta',
39936
+ duplicateQuestion: 'Duplicar pregunta',
39937
+ moveUp: 'Subir pregunta',
39938
+ moveDown: 'Bajar pregunta',
39939
+ doneEditing: 'Listo',
39940
+ untitledQuestion: 'Pregunta sin texto',
39941
+ requiredTag: 'Obligatoria',
39942
+ missingOptions: 'Hay preguntas de opciones sin ninguna opción cargada.',
39194
39943
  save: 'Guardar encuesta',
39195
39944
  noTitle: 'Ponle un título a la encuesta',
39196
39945
  noQuestions: 'Agrega al menos una pregunta',
@@ -39205,10 +39954,16 @@ const SURVEY_BUILDER_I18N = {
39205
39954
  subtitlePlaceholder: 'E.g: Help us improve by answering these questions',
39206
39955
  questionsLabel: 'Questions',
39207
39956
  questionLabelPlaceholder: 'Write the question',
39208
- typeEmoji: 'Emoji',
39209
- typeText: 'Free text',
39210
39957
  addQuestion: 'Add question',
39211
- removeQuestion: 'Remove',
39958
+ removeQuestion: 'Remove question',
39959
+ editQuestion: 'Edit question',
39960
+ duplicateQuestion: 'Duplicate question',
39961
+ moveUp: 'Move question up',
39962
+ moveDown: 'Move question down',
39963
+ doneEditing: 'Done',
39964
+ untitledQuestion: 'Question with no text',
39965
+ requiredTag: 'Required',
39966
+ missingOptions: 'Some choice questions have no options set.',
39212
39967
  save: 'Save survey',
39213
39968
  noTitle: 'Give the survey a title',
39214
39969
  noQuestions: 'Add at least one question',
@@ -39219,6 +39974,7 @@ const SURVEY_BUILDER_I18N = {
39219
39974
  };
39220
39975
 
39221
39976
  const NAMESPACE$1 = 'SurveyBuilder';
39977
+ const EDITOR_NAMESPACE = 'FieldSchemaEditor';
39222
39978
  const DEFAULT_TYPE_PREFIX = 'survey';
39223
39979
  let rowUid = 0;
39224
39980
  /**
@@ -39229,11 +39985,14 @@ let rowUid = 0;
39229
39985
  * true` (cada respuesta se agrega, no se revisa una por una) y el `EntityRef`
39230
39986
  * que le pasa el consumer (ej. el evento de bingo dueño de la encuesta).
39231
39987
  *
39232
- * v1 solo ofrece los dos tipos de pregunta que `val-survey-response` sabe
39233
- * renderizar: emoji-rating y texto libre. Reusa `RequestFormBuilderService`
39234
- * (`toToken`/`uniqueFieldName`/`createFieldSchema`) para el nombre de campo
39235
- * es la misma primitiva que ya arma el formulario dinámico de `request`, no
39236
- * una reimplementación.
39988
+ * Cada pregunta se edita con `val-field-schema-editor`, el mismo editor que usa
39989
+ * el checkout de bingo de ahí que una pregunta tenga tipo, placeholder, nota
39990
+ * y opciones, no solo texto. La v1 de este componente tenía su propio editor
39991
+ * pobre (dos tipos, sin opciones); la Fase 5 del ADR-092 lo reemplazó por el
39992
+ * organism compartido en vez de mejorarlo por duplicado.
39993
+ *
39994
+ * El catálogo que ofrece sale de `SURVEY_QUESTION_TYPES` — acotado a lo que
39995
+ * `val-survey-response` sabe renderizar, no a lo que el editor sabe editar.
39237
39996
  */
39238
39997
  class SurveyBuilderComponent {
39239
39998
  constructor() {
@@ -39243,15 +40002,21 @@ class SurveyBuilderComponent {
39243
40002
  this.requests = inject(RequestService);
39244
40003
  this.formBuilder = inject(RequestFormBuilderService);
39245
40004
  this.errors = inject(ValtechErrorService);
39246
- this.emojiType = FIELD_TYPE_EMOJI_RATING;
39247
40005
  this.titleControl = new FormControl('', { nonNullable: true });
39248
40006
  this.subtitleControl = new FormControl('', { nonNullable: true });
39249
40007
  this.questions = signal([]);
40008
+ this.editingId = signal('');
40009
+ this.validationError = signal('');
39250
40010
  this.saving = signal(false);
39251
- this.rowControls = new Map();
39252
40011
  if (!this.i18n.hasNamespace(NAMESPACE$1)) {
39253
40012
  this.i18n.registerDefaults(NAMESPACE$1, SURVEY_BUILDER_I18N);
39254
40013
  }
40014
+ // El nombre del tipo de cada pregunta se muestra en la fila colapsada, que
40015
+ // dibuja este componente y no el editor — si nadie montó un editor todavía,
40016
+ // el namespace no existiría y la fila mostraría la clave cruda.
40017
+ if (!this.i18n.hasNamespace(EDITOR_NAMESPACE)) {
40018
+ this.i18n.registerDefaults(EDITOR_NAMESPACE, FIELD_SCHEMA_EDITOR_I18N);
40019
+ }
39255
40020
  }
39256
40021
  async ngOnInit() {
39257
40022
  if (!this.props.typeId)
@@ -39270,53 +40035,129 @@ class SurveyBuilderComponent {
39270
40035
  });
39271
40036
  }
39272
40037
  }
39273
- rowFromField(f) {
39274
- const id = `row-${rowUid++}`;
39275
- this.rowControls.set(id, new FormControl(f.label ?? '', { nonNullable: true }));
40038
+ t(key) {
40039
+ this.i18n.lang();
40040
+ return this.i18n.t(key, NAMESPACE$1);
40041
+ }
40042
+ typeLabel(type) {
40043
+ this.i18n.lang();
40044
+ return this.i18n.t(`type${type}`, EDITOR_NAMESPACE);
40045
+ }
40046
+ questionTypes() {
40047
+ return this.props.questionTypes?.length ? this.props.questionTypes : SURVEY_QUESTION_TYPES;
40048
+ }
40049
+ editorProps(row) {
40050
+ return { mode: 'edit', field: row, allowedTypes: this.questionTypes() };
40051
+ }
40052
+ iconAction(token, icon, label, disabled = false, color = 'dark') {
39276
40053
  return {
39277
- id,
39278
- label: f.label ?? '',
39279
- type: f.type === FIELD_TYPE_EMOJI_RATING ? 'EMOJI_RATING' : 'TEXTAREA',
39280
- required: !!f.required,
40054
+ token,
40055
+ text: '',
40056
+ ariaLabel: label,
40057
+ color,
40058
+ fill: 'clear',
40059
+ shape: 'round',
40060
+ size: 'small',
40061
+ type: 'button',
40062
+ state: disabled ? ComponentStates.DISABLED : ComponentStates.ENABLED,
40063
+ icon: { name: icon, slot: 'icon-only' },
39281
40064
  };
39282
40065
  }
39283
- rowLabelControl(row) {
39284
- let control = this.rowControls.get(row.id);
39285
- if (!control) {
39286
- control = new FormControl(row.label, { nonNullable: true });
39287
- this.rowControls.set(row.id, control);
39288
- }
39289
- return control;
39290
- }
39291
40066
  addQuestion() {
39292
- const id = `row-${rowUid++}`;
39293
- this.rowControls.set(id, new FormControl('', { nonNullable: true }));
39294
- this.questions.update(rows => [...rows, { id, label: '', type: 'TEXTAREA', required: true }]);
40067
+ const row = {
40068
+ id: `row-${rowUid++}`,
40069
+ name: '',
40070
+ label: '',
40071
+ placeholderText: '',
40072
+ hintText: '',
40073
+ type: this.questionTypes()[0],
40074
+ required: true,
40075
+ };
40076
+ this.questions.update(rows => [...rows, row]);
40077
+ // Una pregunta recién agregada está vacía: abrirla es el único paso
40078
+ // siguiente posible, y dejarla cerrada la haría ver como una fila rota.
40079
+ this.editingId.set(row.id);
40080
+ }
40081
+ toggleEdit(row) {
40082
+ this.editingId.update(current => (current === row.id ? '' : row.id));
39295
40083
  }
39296
40084
  removeQuestion(row) {
39297
- this.rowControls.delete(row.id);
39298
40085
  this.questions.update(rows => rows.filter(r => r.id !== row.id));
40086
+ if (this.editingId() === row.id)
40087
+ this.editingId.set('');
40088
+ }
40089
+ duplicate(row) {
40090
+ const copy = {
40091
+ ...row,
40092
+ id: `row-${rowUid++}`,
40093
+ // Sin token propio la copia pisaría las respuestas del original: dos
40094
+ // campos con el mismo `name` son el mismo campo para el backend.
40095
+ name: this.formBuilder.uniqueFieldName(row.name || row.label, this.questions()),
40096
+ options: row.options ? row.options.map(o => ({ ...o })) : undefined,
40097
+ };
40098
+ this.questions.update(rows => {
40099
+ const index = rows.findIndex(r => r.id === row.id);
40100
+ return [...rows.slice(0, index + 1), copy, ...rows.slice(index + 1)];
40101
+ });
40102
+ }
40103
+ move(index, delta) {
40104
+ const target = index + delta;
40105
+ this.questions.update(rows => {
40106
+ if (target < 0 || target >= rows.length)
40107
+ return rows;
40108
+ const next = [...rows];
40109
+ [next[index], next[target]] = [next[target], next[index]];
40110
+ return next;
40111
+ });
39299
40112
  }
39300
- setType(row, type) {
39301
- this.questions.update(rows => rows.map(r => (r.id === row.id ? { ...r, type } : r)));
40113
+ applyQuestion(field) {
40114
+ this.questions.update(rows => rows.map(r => r.id === field.id
40115
+ ? {
40116
+ ...field,
40117
+ name: this.formBuilder.uniqueFieldName(field.name || field.label, rows.filter(o => o.id !== r.id)),
40118
+ }
40119
+ : r));
40120
+ this.editingId.set('');
40121
+ this.validationError.set('');
39302
40122
  }
39303
40123
  async save() {
39304
40124
  if (this.saving())
39305
40125
  return;
40126
+ this.validationError.set('');
39306
40127
  const title = this.titleControl.value.trim();
39307
40128
  if (!title) {
39308
40129
  this.titleControl.markAsTouched();
40130
+ this.validationError.set(this.t('noTitle'));
39309
40131
  return;
39310
40132
  }
39311
40133
  const rows = this.questions();
39312
- const labels = rows.map(r => (this.rowControls.get(r.id)?.value ?? '').trim());
39313
- if (rows.length === 0 || labels.some(l => !l))
40134
+ if (!rows.length) {
40135
+ this.validationError.set(this.t('noQuestions'));
40136
+ return;
40137
+ }
40138
+ if (rows.some(r => !r.label.trim())) {
40139
+ this.validationError.set(this.t('emptyQuestionLabel'));
39314
40140
  return;
40141
+ }
40142
+ // El editor ya lo valida al cerrar cada pregunta, pero una pregunta abierta
40143
+ // (o cargada de una encuesta vieja) puede llegar acá sin opciones.
40144
+ if (rows.some(r => FIELD_TYPES_WITH_OPTIONS.includes(r.type) && !r.options?.length)) {
40145
+ this.validationError.set(this.t('missingOptions'));
40146
+ return;
40147
+ }
39315
40148
  this.saving.set(true);
39316
40149
  const fieldSchema = [];
39317
40150
  rows.forEach((row, index) => {
39318
- const name = this.formBuilder.uniqueFieldName(labels[index], fieldSchema);
39319
- fieldSchema.push(this.formBuilder.createFieldSchema({ name, label: labels[index], type: row.type, required: row.required }, index));
40151
+ const name = this.formBuilder.uniqueFieldName(row.name || row.label, fieldSchema);
40152
+ fieldSchema.push(this.formBuilder.createFieldSchema({
40153
+ name,
40154
+ label: row.label.trim(),
40155
+ type: row.type,
40156
+ required: row.required,
40157
+ placeholderText: row.placeholderText,
40158
+ hintText: row.hintText,
40159
+ options: row.options,
40160
+ }, index));
39320
40161
  });
39321
40162
  const typeId = this.props.typeId ?? `${this.props.typePrefix ?? DEFAULT_TYPE_PREFIX}:${this.formBuilder.toToken(title)}`;
39322
40163
  const subtitle = this.subtitleControl.value.trim();
@@ -39346,64 +40187,84 @@ class SurveyBuilderComponent {
39346
40187
  });
39347
40188
  }
39348
40189
  }
39349
- t(key) {
39350
- return this.i18n.t(key, NAMESPACE$1);
40190
+ rowFromField(f) {
40191
+ return {
40192
+ id: `row-${rowUid++}`,
40193
+ name: f.name,
40194
+ label: f.label ?? '',
40195
+ placeholderText: f.placeholderText ?? '',
40196
+ hintText: f.hintText ?? '',
40197
+ type: f.type,
40198
+ required: !!f.required,
40199
+ options: f.options,
40200
+ };
39351
40201
  }
39352
40202
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
39353
40203
  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" }, ngImport: i0, template: `
39354
40204
  <div class="survey-builder">
39355
- <val-text-input [props]="{ control: titleControl, label: t('titleLabel'), placeholder: t('titlePlaceholder') }" />
39356
- <val-text-input
39357
- [props]="{ control: subtitleControl, label: t('subtitleLabel'), placeholder: t('subtitlePlaceholder') }"
39358
- />
40205
+ <val-form-field [label]="t('titleLabel')">
40206
+ <val-text-input [props]="{ control: titleControl, placeholder: t('titlePlaceholder') }" />
40207
+ </val-form-field>
40208
+ <val-form-field [label]="t('subtitleLabel')">
40209
+ <val-text-input [props]="{ control: subtitleControl, placeholder: t('subtitlePlaceholder') }" />
40210
+ </val-form-field>
39359
40211
 
39360
40212
  <div class="survey-builder__questions">
39361
40213
  <span class="survey-builder__questions-label">{{ t('questionsLabel') }}</span>
39362
40214
 
39363
- @for (row of questions(); track row.id) {
40215
+ @if (!questions().length) {
40216
+ <p class="survey-builder__empty">{{ t('noQuestions') }}</p>
40217
+ }
40218
+
40219
+ @for (row of questions(); track row.id; let i = $index; let first = $first; let last = $last) {
39364
40220
  <div class="survey-builder__row">
39365
- <val-text-input [props]="{ control: rowLabelControl(row), placeholder: t('questionLabelPlaceholder') }" />
39366
- <div class="survey-builder__row-actions">
39367
- <val-button
39368
- [props]="{
39369
- token: 'q-type-emoji-' + row.id,
39370
- text: t('typeEmoji'),
39371
- color: row.type === emojiType ? 'primary' : 'medium',
39372
- fill: row.type === emojiType ? 'solid' : 'outline',
39373
- shape: 'round',
39374
- size: 'small',
39375
- type: 'button',
39376
- state: 'ENABLED',
39377
- }"
39378
- (onClick)="setType(row, emojiType)"
39379
- />
39380
- <val-button
39381
- [props]="{
39382
- token: 'q-type-text-' + row.id,
39383
- text: t('typeText'),
39384
- color: row.type === 'TEXTAREA' ? 'primary' : 'medium',
39385
- fill: row.type === 'TEXTAREA' ? 'solid' : 'outline',
39386
- shape: 'round',
39387
- size: 'small',
39388
- type: 'button',
39389
- state: 'ENABLED',
39390
- }"
39391
- (onClick)="setType(row, 'TEXTAREA')"
39392
- />
39393
- <val-button
39394
- [props]="{
39395
- token: 'q-remove-' + row.id,
39396
- text: t('removeQuestion'),
39397
- color: 'danger',
39398
- fill: 'clear',
39399
- shape: 'round',
39400
- size: 'small',
39401
- type: 'button',
39402
- state: 'ENABLED',
39403
- }"
39404
- (onClick)="removeQuestion(row)"
39405
- />
40221
+ <div class="survey-builder__row-head">
40222
+ <div class="survey-builder__row-text">
40223
+ <strong>{{ row.label || t('untitledQuestion') }}</strong>
40224
+ <span>{{ typeLabel(row.type) }}{{ row.required ? ' · ' + t('requiredTag') : '' }}</span>
40225
+ </div>
40226
+ <div class="survey-builder__row-actions">
40227
+ <val-button
40228
+ [props]="iconAction('q-up-' + row.id, 'arrow-up-outline', t('moveUp'), first)"
40229
+ (onClick)="move(i, -1)"
40230
+ />
40231
+ <val-button
40232
+ [props]="iconAction('q-down-' + row.id, 'arrow-down-outline', t('moveDown'), last)"
40233
+ (onClick)="move(i, 1)"
40234
+ />
40235
+ <val-button
40236
+ [props]="iconAction('q-copy-' + row.id, 'copy-outline', t('duplicateQuestion'))"
40237
+ (onClick)="duplicate(row)"
40238
+ />
40239
+ <val-button
40240
+ [props]="iconAction('q-edit-' + row.id, 'create-outline', t('editQuestion'))"
40241
+ (onClick)="toggleEdit(row)"
40242
+ />
40243
+ <val-button
40244
+ [props]="iconAction('q-remove-' + row.id, 'trash-outline', t('removeQuestion'), false, 'danger')"
40245
+ (onClick)="removeQuestion(row)"
40246
+ />
40247
+ </div>
39406
40248
  </div>
40249
+
40250
+ @if (editingId() === row.id) {
40251
+ <div class="survey-builder__row-editor">
40252
+ <val-field-schema-editor #editor [props]="editorProps(row)" (save)="applyQuestion($event)" />
40253
+ <val-button
40254
+ [props]="{
40255
+ token: 'q-done-' + row.id,
40256
+ text: t('doneEditing'),
40257
+ color: 'dark',
40258
+ fill: 'solid',
40259
+ shape: 'round',
40260
+ size: 'default',
40261
+ type: 'button',
40262
+ state: 'ENABLED',
40263
+ }"
40264
+ (onClick)="editor.submit()"
40265
+ />
40266
+ </div>
40267
+ }
39407
40268
  </div>
39408
40269
  }
39409
40270
 
@@ -39423,6 +40284,10 @@ class SurveyBuilderComponent {
39423
40284
  />
39424
40285
  </div>
39425
40286
 
40287
+ @if (validationError()) {
40288
+ <p class="survey-builder__error">{{ validationError() }}</p>
40289
+ }
40290
+
39426
40291
  <val-button
39427
40292
  [props]="{
39428
40293
  token: 'survey-builder-save',
@@ -39430,6 +40295,7 @@ class SurveyBuilderComponent {
39430
40295
  color: 'dark',
39431
40296
  fill: 'solid',
39432
40297
  shape: 'round',
40298
+ size: 'large',
39433
40299
  expand: 'block',
39434
40300
  type: 'button',
39435
40301
  state: saving() ? 'WORKING' : 'ENABLED',
@@ -39437,64 +40303,82 @@ class SurveyBuilderComponent {
39437
40303
  (onClick)="save()"
39438
40304
  />
39439
40305
  </div>
39440
- `, 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__questions-label{font-size:.8125rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--ion-color-medium, #92949c)}.survey-builder__row{display:flex;flex-direction:column;gap:8px;padding:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:12px;background:var(--ion-card-background, var(--ion-background-color, #fff))}.survey-builder__row-actions{display:flex;gap:8px;flex-wrap:wrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: TextInputComponent, selector: "val-text-input", inputs: ["preset", "props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
40306
+ `, 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__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: 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"] }] }); }
39441
40307
  }
39442
40308
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyBuilderComponent, decorators: [{
39443
40309
  type: Component,
39444
- args: [{ selector: 'val-survey-builder', standalone: true, imports: [CommonModule, ReactiveFormsModule, TextInputComponent, ButtonComponent], template: `
40310
+ args: [{ selector: 'val-survey-builder', standalone: true, imports: [
40311
+ CommonModule,
40312
+ ReactiveFormsModule,
40313
+ TextInputComponent,
40314
+ ButtonComponent,
40315
+ FormFieldComponent,
40316
+ FieldSchemaEditorComponent,
40317
+ ], template: `
39445
40318
  <div class="survey-builder">
39446
- <val-text-input [props]="{ control: titleControl, label: t('titleLabel'), placeholder: t('titlePlaceholder') }" />
39447
- <val-text-input
39448
- [props]="{ control: subtitleControl, label: t('subtitleLabel'), placeholder: t('subtitlePlaceholder') }"
39449
- />
40319
+ <val-form-field [label]="t('titleLabel')">
40320
+ <val-text-input [props]="{ control: titleControl, placeholder: t('titlePlaceholder') }" />
40321
+ </val-form-field>
40322
+ <val-form-field [label]="t('subtitleLabel')">
40323
+ <val-text-input [props]="{ control: subtitleControl, placeholder: t('subtitlePlaceholder') }" />
40324
+ </val-form-field>
39450
40325
 
39451
40326
  <div class="survey-builder__questions">
39452
40327
  <span class="survey-builder__questions-label">{{ t('questionsLabel') }}</span>
39453
40328
 
39454
- @for (row of questions(); track row.id) {
40329
+ @if (!questions().length) {
40330
+ <p class="survey-builder__empty">{{ t('noQuestions') }}</p>
40331
+ }
40332
+
40333
+ @for (row of questions(); track row.id; let i = $index; let first = $first; let last = $last) {
39455
40334
  <div class="survey-builder__row">
39456
- <val-text-input [props]="{ control: rowLabelControl(row), placeholder: t('questionLabelPlaceholder') }" />
39457
- <div class="survey-builder__row-actions">
39458
- <val-button
39459
- [props]="{
39460
- token: 'q-type-emoji-' + row.id,
39461
- text: t('typeEmoji'),
39462
- color: row.type === emojiType ? 'primary' : 'medium',
39463
- fill: row.type === emojiType ? 'solid' : 'outline',
39464
- shape: 'round',
39465
- size: 'small',
39466
- type: 'button',
39467
- state: 'ENABLED',
39468
- }"
39469
- (onClick)="setType(row, emojiType)"
39470
- />
39471
- <val-button
39472
- [props]="{
39473
- token: 'q-type-text-' + row.id,
39474
- text: t('typeText'),
39475
- color: row.type === 'TEXTAREA' ? 'primary' : 'medium',
39476
- fill: row.type === 'TEXTAREA' ? 'solid' : 'outline',
39477
- shape: 'round',
39478
- size: 'small',
39479
- type: 'button',
39480
- state: 'ENABLED',
39481
- }"
39482
- (onClick)="setType(row, 'TEXTAREA')"
39483
- />
39484
- <val-button
39485
- [props]="{
39486
- token: 'q-remove-' + row.id,
39487
- text: t('removeQuestion'),
39488
- color: 'danger',
39489
- fill: 'clear',
39490
- shape: 'round',
39491
- size: 'small',
39492
- type: 'button',
39493
- state: 'ENABLED',
39494
- }"
39495
- (onClick)="removeQuestion(row)"
39496
- />
40335
+ <div class="survey-builder__row-head">
40336
+ <div class="survey-builder__row-text">
40337
+ <strong>{{ row.label || t('untitledQuestion') }}</strong>
40338
+ <span>{{ typeLabel(row.type) }}{{ row.required ? ' · ' + t('requiredTag') : '' }}</span>
40339
+ </div>
40340
+ <div class="survey-builder__row-actions">
40341
+ <val-button
40342
+ [props]="iconAction('q-up-' + row.id, 'arrow-up-outline', t('moveUp'), first)"
40343
+ (onClick)="move(i, -1)"
40344
+ />
40345
+ <val-button
40346
+ [props]="iconAction('q-down-' + row.id, 'arrow-down-outline', t('moveDown'), last)"
40347
+ (onClick)="move(i, 1)"
40348
+ />
40349
+ <val-button
40350
+ [props]="iconAction('q-copy-' + row.id, 'copy-outline', t('duplicateQuestion'))"
40351
+ (onClick)="duplicate(row)"
40352
+ />
40353
+ <val-button
40354
+ [props]="iconAction('q-edit-' + row.id, 'create-outline', t('editQuestion'))"
40355
+ (onClick)="toggleEdit(row)"
40356
+ />
40357
+ <val-button
40358
+ [props]="iconAction('q-remove-' + row.id, 'trash-outline', t('removeQuestion'), false, 'danger')"
40359
+ (onClick)="removeQuestion(row)"
40360
+ />
40361
+ </div>
39497
40362
  </div>
40363
+
40364
+ @if (editingId() === row.id) {
40365
+ <div class="survey-builder__row-editor">
40366
+ <val-field-schema-editor #editor [props]="editorProps(row)" (save)="applyQuestion($event)" />
40367
+ <val-button
40368
+ [props]="{
40369
+ token: 'q-done-' + row.id,
40370
+ text: t('doneEditing'),
40371
+ color: 'dark',
40372
+ fill: 'solid',
40373
+ shape: 'round',
40374
+ size: 'default',
40375
+ type: 'button',
40376
+ state: 'ENABLED',
40377
+ }"
40378
+ (onClick)="editor.submit()"
40379
+ />
40380
+ </div>
40381
+ }
39498
40382
  </div>
39499
40383
  }
39500
40384
 
@@ -39514,6 +40398,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
39514
40398
  />
39515
40399
  </div>
39516
40400
 
40401
+ @if (validationError()) {
40402
+ <p class="survey-builder__error">{{ validationError() }}</p>
40403
+ }
40404
+
39517
40405
  <val-button
39518
40406
  [props]="{
39519
40407
  token: 'survey-builder-save',
@@ -39521,6 +40409,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
39521
40409
  color: 'dark',
39522
40410
  fill: 'solid',
39523
40411
  shape: 'round',
40412
+ size: 'large',
39524
40413
  expand: 'block',
39525
40414
  type: 'button',
39526
40415
  state: saving() ? 'WORKING' : 'ENABLED',
@@ -39528,7 +40417,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
39528
40417
  (onClick)="save()"
39529
40418
  />
39530
40419
  </div>
39531
- `, 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__questions-label{font-size:.8125rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--ion-color-medium, #92949c)}.survey-builder__row{display:flex;flex-direction:column;gap:8px;padding:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:12px;background:var(--ion-card-background, var(--ion-background-color, #fff))}.survey-builder__row-actions{display:flex;gap:8px;flex-wrap:wrap}\n"] }]
40420
+ `, 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__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"] }]
39532
40421
  }], ctorParameters: () => [], propDecorators: { props: [{
39533
40422
  type: Input
39534
40423
  }], saved: [{
@@ -89150,66 +90039,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
89150
90039
  * aporta la sesión de escaneo, el feedback y el historial.
89151
90040
  */
89152
90041
 
89153
- /**
89154
- * Primitivo de campo de formulario: label (estilo val-form) + contenido proyectado.
89155
- *
89156
- * Usar cuando necesitás un campo fuera de val-form pero con el mismo estilo:
89157
- * image-picker, date-picker, selectores custom, chips, etc.
89158
- *
89159
- * NOTA: para ion-input / ion-textarea NO usar ng-content projection — el Shadow DOM
89160
- * de Ionic no responde al grid del host. Usá en cambio un `<div class="pf-field">`
89161
- * plano en el template con `<p class="pf-label">` + ion-input directo.
89162
- *
89163
- * ```html
89164
- * <val-form-field label="Imagen">
89165
- * <app-image-picker ... />
89166
- * </val-form-field>
89167
- *
89168
- * <!-- Para ion-input: NO val-form-field, usar div plano -->
89169
- * <div class="pf-field">
89170
- * <p class="pf-label">Nombre</p>
89171
- * <ion-input fill="outline" ... />
89172
- * </div>
89173
- * ```
89174
- *
89175
- * El label usa el mismo val-title que val-form (size=small, color=dark, bold=false).
89176
- * El spacing entre campos se controla con --val-form-field-gap (default 0.5rem).
89177
- * El padding horizontal se hereda vía --val-form-field-padding (default 0).
89178
- * Setearlo en el contenedor padre para consistencia sin override en cada campo:
89179
- * `.my-card { --val-form-field-padding: 0 16px; }`
89180
- */
89181
- class FormFieldComponent {
89182
- constructor() {
89183
- this.label = input('');
89184
- this.titleProps = computed(() => ({
89185
- content: this.label(),
89186
- size: 'small',
89187
- color: 'dark',
89188
- bold: false,
89189
- }));
89190
- }
89191
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
89192
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FormFieldComponent, isStandalone: true, selector: "val-form-field", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
89193
- @if (label()) {
89194
- <div class="vff-label">
89195
- <val-title [props]="titleProps()" />
89196
- </div>
89197
- }
89198
- <ng-content />
89199
- `, isInline: true, styles: [":host{display:grid;grid-template-columns:1fr;width:100%;box-sizing:border-box;margin:var(--val-form-field-gap, .5rem) 0;padding:var(--val-form-field-padding, 0)}.vff-label{margin-bottom:.25rem}.vff-label ::ng-deep p{margin:0}\n"], dependencies: [{ kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
89200
- }
89201
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FormFieldComponent, decorators: [{
89202
- type: Component,
89203
- args: [{ selector: 'val-form-field', standalone: true, imports: [TitleComponent], template: `
89204
- @if (label()) {
89205
- <div class="vff-label">
89206
- <val-title [props]="titleProps()" />
89207
- </div>
89208
- }
89209
- <ng-content />
89210
- `, styles: [":host{display:grid;grid-template-columns:1fr;width:100%;box-sizing:border-box;margin:var(--val-form-field-gap, .5rem) 0;padding:var(--val-form-field-padding, 0)}.vff-label{margin-bottom:.25rem}.vff-label ::ng-deep p{margin:0}\n"] }]
89211
- }] });
89212
-
89213
90042
  /**
89214
90043
  * Token de inyeccion para la configuracion del Chat.
89215
90044
  */
@@ -92023,5 +92852,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
92023
92852
  * Generated bundle index. Do not edit.
92024
92853
  */
92025
92854
 
92026
- export { ACCESS_REQUEST_TYPE, ACCESS_REQUEST_VIEW_I18N, ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, APP_VERSION_PLATFORM_PLUGIN, APP_VERSION_REMOTE_PLUGIN, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AboutViewComponent, AccessControlService, AccessRequestViewComponent, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimalCardComponent, AnimatedTerminalComponent, ApiKeyCreateModalComponent, ApiKeyService, ApiKeysModalComponent, ApiKeysViewComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BackButtonComponent, BannerComponent, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, BubbleBlitzGameComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CanAccessDirective, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, ChatComposerComponent, ChatInputComponent, ChatWindowComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ChipSelectComponent, ClientTelemetryService, CodeDisplayComponent, CodeValidatorComponent, CollectionsService, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CommsPreferencesService, CommsPreferencesSettingsComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ConfirmationDialogV2Component, ContainerComponent, ContentLoaderComponent, ContentReactionComponent, ContentReactionModalComponent, ContentService, ContentTransformer, ConversationListItemComponent, ConversationService, CookieBannerComponent, CookieSettingsComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_BUTTON_PRESETS, DEFAULT_CANCEL_BUTTON, DEFAULT_CANONICAL_FIELD_ALIASES, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMOJI_RATING_FACES, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_LOGIN_LOGO, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_POST_UPDATE_GRACE_MS, DEFAULT_PRESETS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DangerSectionComponent, DataTableComponent, DatasetPaginationService, DateInputComponent, DatePickerComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EXPECTED_NOT_FOUND, EditOrgModalComponent, EmojiRatingComponent, EmptyStateComponent, EntityCardComponent, EntityFeedService, EntradaSerializada, ExpandableTextComponent, ExternalBrowserService, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeatureControlService, FeatureGuardDirective, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FieldListComponent, FieldOptionsEditorComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FolderTabsComponent, FontSizeOption, FontSizeSelectorComponent, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormFieldComponent, FormSchemaBuilderService, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GAME_AVATAR_CATALOG_SIZE_PER_STYLE, GameAvatarComponent, GameProfileService, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, GroupMembersComponent, GroupPickerComponent, GroupsService, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandleService, HandoffService, HapticsService, HasPermissionDirective, HeaderActionsService, HeaderComponent, HintComponent, HorizontalScrollComponent, HrefComponent, HtmlViewerModalComponent, I18nService, IMAGE_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, IONIC_COLORS$5 as IONIC_COLORS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, KNOWN_ROUTES, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGGED_IN_HINT_COOKIE, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LanguageSelectorV2Component, LayeredCardComponent, LegalContentService, LegalLinkService, LightRippleDirective, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LiveReadFallbackService, LoadMoreComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METADATA_LIST_DEFAULTS, META_SCHEMA_VERSION, METRIC_CARD_DEFAULTS, MINI_GAMES_I18N, MINI_GAME_PLAYER_AVATARS, MINI_GAME_PLAYER_COLORS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MediaViewerModalComponent, MemberCardComponent, MemberDetailModalComponent, MemberImportModalComponent, MemoryGameComponent, MenuComponent, MessageBubbleComponent, MessagingService, MetaService, MetadataListComponent, MeteringService, MetricCardComponent, MfaModalComponent, MiniGameCalloutComponent, MiniGamePlayerProfileService, MiniGameScorePopComponent, MiniGamesMenuComponent, ModalLayoutComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NoticeComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberPickerComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OperationReferenceComponent, OptionCardsComponent, OptionSheetComponent, OptionSheetService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PERSONA_CONFIG, PLATFORM_CONFIGS, POST_UPDATE_TS_KEY, PageBlockComponent, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PdfService, PermissionCatalogService, PermissionSelectorComponent, PermissionsModalComponent, PermissionsViewComponent, PersonaService, PhoneDisplayComponent, PhoneFormatService, PhoneInputComponent, PickerV2Component, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PricingTableComponent, ProcessLinksPipe, ProfileCardComponent, ProfileContentComponent, ProfileModalComponent, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrBrandValidationError, QrCodeComponent, QrGeneratorService, QrScannerComponent, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RbacService, ReactionBarComponent, ReactionsService, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestFormComponent, RequestModalComponent, RequestReviewPanelComponent, RequestService, RetroAudioService, RichEditorComponent, RightsFooterComponent, RoleManagerComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SectionHeaderComponent, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SelectSearchPickerModalComponent, SessionListModalComponent, SessionService, SessionTransitionOverlayComponent, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimonGameComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SplashComponent, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SupportTicketCtaComponent, SurveyBuilderComponent, SurveyResponseComponent, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeSelectorComponent, ThemeService, ThreadPanelComponent, TicketCardComponent, TicketCardImageService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, TypingIndicatorComponent, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UsageMetersComponent, UsageService, UserAvatarComponent, UsernameInputComponent, VALTECH_ACCESS_FEATURES, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_CHAT_CONFIG, VALTECH_COLLECTIONS_CONFIG, VALTECH_COMMS_PREFERENCES_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_COPYRIGHT_TEMPLATE, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DIAGRAMS, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_LEGAL_ENTITY, VALTECH_MENU_I18N, VALTECH_NETWORK_ERROR_KEY, VALTECH_REACTIONS_CONFIG, VALTECH_SETTINGS_MENU_LINKS, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VALTECH_WHATSAPP_CONFIG, VAL_REGISTERED_ICONS, VERSION, ValCommentThreadComponent, ValQuotaWarningComponent, ValtechErrorService, VerifyViewComponent, VideoPlayerComponent, VideoUploadService, WhatsappFabComponent, WhatsappService, WizardComponent, WizardFooterComponent, WorkflowService, accessGuard, accessGuardFromRoute, applyDefaultValueToControl, articleToTiptapDoc, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildPlatformMenu, buildSettingsCards, buildSideNavItemsFromBottomNav, button, canSubmitRequestType, classifyChip, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGameAvatarCatalog, createGameAvatarProps, createGlowCardProps, createInitialDatasetState, createInitialPaginationState, createNumberFromToField, createPageState, createPermissionLabeler, createRefreshableStream, createTitleProps, datasetPageFromLegacyCursor, defaultQrBrand, docs, errorLoggingInterceptor, evaluateValtechAccess, extractPathParams, firmaDeSesionDeCustomToken, formatClockTime, formatDateSeparator, formatRelativeTime, gameAvatarCatalogEntryToMetadata, gameAvatarDataUri, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, groupPermissionsByScope, guestGuard, hasEmulators, iconButton, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isIonicColor, isKnownRoute, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, providePersona, provideSplashScreen, provideValtechAboutRoutes, provideValtechAccessFeatures, provideValtechAccountRoutes, provideValtechAds, provideValtechApiKeysRoutes, provideValtechAppConfig, provideValtechAppVersion, provideValtechAppVersionHttp, provideValtechAuth, provideValtechAuthInterceptor, provideValtechChat, provideValtechCollections, provideValtechCommsPreferences, provideValtechContent, provideValtechDebugConsole, provideValtechDiagrams, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationClickActions, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechReactions, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, provideValtechWhatsapp, qrContrastRatio, qrErrorCorrectionFor, query, rbacGuard, renderGameAvatarSvg, renderPatternSvgInner, replaceSpecialChars, requestSubmitMode, resolveColor, resolveCopyrightTemplate, resolveInputDefaultValue, resolveIonicColor, resolveWebBaseUrl, roleGuard, roleOf, selectableRequestTypes, storagePaths, suggestEmailFix, superAdminGuard, supportedGameAvatarStyles, tiptapDocToArticle, tiptapDocToArticleElements, toArticle, validateQrBrand, validateRoutes };
92855
+ export { ACCESS_REQUEST_TYPE, ACCESS_REQUEST_VIEW_I18N, ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, APP_VERSION_PLATFORM_PLUGIN, APP_VERSION_REMOTE_PLUGIN, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AboutViewComponent, AccessControlService, AccessRequestViewComponent, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimalCardComponent, AnimatedTerminalComponent, ApiKeyCreateModalComponent, ApiKeyService, ApiKeysModalComponent, ApiKeysViewComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BackButtonComponent, BannerComponent, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, BubbleBlitzGameComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CanAccessDirective, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, ChatComposerComponent, ChatInputComponent, ChatWindowComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ChipSelectComponent, ClientTelemetryService, CodeDisplayComponent, CodeValidatorComponent, CollectionsService, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CommsPreferencesService, CommsPreferencesSettingsComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ConfirmationDialogV2Component, ContainerComponent, ContentLoaderComponent, ContentReactionComponent, ContentReactionModalComponent, ContentService, ContentTransformer, ConversationListItemComponent, ConversationService, CookieBannerComponent, CookieSettingsComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_BUTTON_PRESETS, DEFAULT_CANCEL_BUTTON, DEFAULT_CANONICAL_FIELD_ALIASES, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMOJI_RATING_FACES, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_LOGIN_LOGO, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_POST_UPDATE_GRACE_MS, DEFAULT_PRESETS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DangerSectionComponent, DataTableComponent, DatasetPaginationService, DateInputComponent, DatePickerComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EXPECTED_NOT_FOUND, EditOrgModalComponent, EmojiRatingComponent, EmptyStateComponent, EntityCardComponent, EntityFeedService, EntradaSerializada, ExpandableTextComponent, ExternalBrowserService, FEATURES_LIST_DEFAULTS, FIELD_TYPES_WITH_OPTIONS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeatureControlService, FeatureGuardDirective, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FieldListComponent, FieldOptionsEditorComponent, FieldSchemaEditorComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FolderTabsComponent, FontSizeOption, FontSizeSelectorComponent, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormFieldComponent, FormSchemaBuilderService, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GAME_AVATAR_CATALOG_SIZE_PER_STYLE, GameAvatarComponent, GameProfileService, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, GroupMembersComponent, GroupPickerComponent, GroupsService, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandleService, HandoffService, HapticsService, HasPermissionDirective, HeaderActionsService, HeaderComponent, HintComponent, HorizontalScrollComponent, HrefComponent, HtmlViewerModalComponent, I18nService, IMAGE_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, IONIC_COLORS$5 as IONIC_COLORS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, KNOWN_ROUTES, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGGED_IN_HINT_COOKIE, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LanguageSelectorV2Component, LayeredCardComponent, LegalContentService, LegalLinkService, LightRippleDirective, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LiveReadFallbackService, LoadMoreComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METADATA_LIST_DEFAULTS, META_SCHEMA_VERSION, METRIC_CARD_DEFAULTS, MINI_GAMES_I18N, MINI_GAME_PLAYER_AVATARS, MINI_GAME_PLAYER_COLORS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MediaViewerModalComponent, MemberCardComponent, MemberDetailModalComponent, MemberImportModalComponent, MemoryGameComponent, MenuComponent, MessageBubbleComponent, MessagingService, MetaService, MetadataListComponent, MeteringService, MetricCardComponent, MfaModalComponent, MiniGameCalloutComponent, MiniGamePlayerProfileService, MiniGameScorePopComponent, MiniGamesMenuComponent, ModalLayoutComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NoticeComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberPickerComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OperationReferenceComponent, OptionCardsComponent, OptionSheetComponent, OptionSheetService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PERSONA_CONFIG, PLATFORM_CONFIGS, POST_UPDATE_TS_KEY, PageBlockComponent, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PdfService, PermissionCatalogService, PermissionSelectorComponent, PermissionsModalComponent, PermissionsViewComponent, PersonaService, PhoneDisplayComponent, PhoneFormatService, PhoneInputComponent, PickerV2Component, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PricingTableComponent, ProcessLinksPipe, ProfileCardComponent, ProfileContentComponent, ProfileModalComponent, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrBrandValidationError, QrCodeComponent, QrGeneratorService, QrScannerComponent, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RbacService, ReactionBarComponent, ReactionsService, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestFormComponent, RequestModalComponent, RequestReviewPanelComponent, RequestService, RetroAudioService, RichEditorComponent, RightsFooterComponent, RoleManagerComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SURVEY_QUESTION_TYPES, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SectionHeaderComponent, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SelectSearchPickerModalComponent, SessionListModalComponent, SessionService, SessionTransitionOverlayComponent, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimonGameComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SplashComponent, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SupportTicketCtaComponent, SurveyBuilderComponent, SurveyResponseComponent, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeSelectorComponent, ThemeService, ThreadPanelComponent, TicketCardComponent, TicketCardImageService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, TypingIndicatorComponent, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UsageMetersComponent, UsageService, UserAvatarComponent, UsernameInputComponent, VALTECH_ACCESS_FEATURES, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_CHAT_CONFIG, VALTECH_COLLECTIONS_CONFIG, VALTECH_COMMS_PREFERENCES_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_COPYRIGHT_TEMPLATE, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DIAGRAMS, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_LEGAL_ENTITY, VALTECH_MENU_I18N, VALTECH_NETWORK_ERROR_KEY, VALTECH_REACTIONS_CONFIG, VALTECH_SETTINGS_MENU_LINKS, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VALTECH_WHATSAPP_CONFIG, VAL_REGISTERED_ICONS, VERSION, ValCommentThreadComponent, ValQuotaWarningComponent, ValtechErrorService, VerifyViewComponent, VideoPlayerComponent, VideoUploadService, WhatsappFabComponent, WhatsappService, WizardComponent, WizardFooterComponent, WorkflowService, accessGuard, accessGuardFromRoute, applyDefaultValueToControl, articleToTiptapDoc, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildPlatformMenu, buildSettingsCards, buildSideNavItemsFromBottomNav, button, canSubmitRequestType, classifyChip, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGameAvatarCatalog, createGameAvatarProps, createGlowCardProps, createInitialDatasetState, createInitialPaginationState, createNumberFromToField, createPageState, createPermissionLabeler, createRefreshableStream, createTitleProps, datasetPageFromLegacyCursor, defaultQrBrand, docs, errorLoggingInterceptor, evaluateValtechAccess, extractPathParams, firmaDeSesionDeCustomToken, formatClockTime, formatDateSeparator, formatRelativeTime, gameAvatarCatalogEntryToMetadata, gameAvatarDataUri, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, groupPermissionsByScope, guestGuard, hasEmulators, iconButton, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isIonicColor, isKnownRoute, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, providePersona, provideSplashScreen, provideValtechAboutRoutes, provideValtechAccessFeatures, provideValtechAccountRoutes, provideValtechAds, provideValtechApiKeysRoutes, provideValtechAppConfig, provideValtechAppVersion, provideValtechAppVersionHttp, provideValtechAuth, provideValtechAuthInterceptor, provideValtechChat, provideValtechCollections, provideValtechCommsPreferences, provideValtechContent, provideValtechDebugConsole, provideValtechDiagrams, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationClickActions, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechReactions, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, provideValtechWhatsapp, qrContrastRatio, qrErrorCorrectionFor, query, rbacGuard, renderGameAvatarSvg, renderPatternSvgInner, replaceSpecialChars, requestSubmitMode, resolveColor, resolveCopyrightTemplate, resolveInputDefaultValue, resolveIonicColor, resolveWebBaseUrl, roleGuard, roleOf, selectableRequestTypes, storagePaths, suggestEmailFix, superAdminGuard, supportedGameAvatarStyles, tiptapDocToArticle, tiptapDocToArticleElements, toArticle, validateQrBrand, validateRoutes };
92027
92856
  //# sourceMappingURL=valtech-components.mjs.map