valtech-components 4.0.982 → 4.0.984

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 +364 -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 +289 -136
  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 +1108 -231
  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 +99 -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 +36 -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.982';
73
+ const VERSION = '4.0.984';
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,427 @@ 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
+ /**
39600
+ * Emite en cada tecla, sin validar. Existe porque el consumer necesita el
39601
+ * borrador en vivo: si solo tuviera `save`, lo escrito acá no existiría para
39602
+ * él hasta que alguien toque su botón — y lo escrito ES la intención del
39603
+ * usuario, no un borrador que haya que confirmar.
39604
+ */
39605
+ this.changed = new EventEmitter();
39606
+ this.i18n = inject(I18nService);
39607
+ this.formSchemas = inject(FormSchemaBuilderService);
39608
+ this.labelControl = new FormControl('', { nonNullable: true });
39609
+ this.placeholderControl = new FormControl('', { nonNullable: true });
39610
+ this.hintControl = new FormControl('', { nonNullable: true });
39611
+ this.requiredControl = new FormControl(true, { nonNullable: true });
39612
+ this.typeControl = new FormControl('TEXT', { nonNullable: true });
39613
+ this.options = signal([]);
39614
+ this.validationError = signal('');
39615
+ this.selectedPresetId = signal('');
39616
+ /** Se refresca con cada cambio de tipo — `needsOptions` no puede leer el control directo. */
39617
+ this.currentType = signal('TEXT');
39618
+ /** Una vez que se toca el token a mano (o se edita un campo ya guardado) deja de derivarse del label. */
39619
+ this.nameLocked = false;
39620
+ this.name = '';
39621
+ this.fieldId = '';
39622
+ this.typeSelectProps = computed(() => {
39623
+ this.i18n.lang();
39624
+ return {
39625
+ control: this.typeControl,
39626
+ type: InputType.SELECT,
39627
+ state: this.state(),
39628
+ placeholder: this.t('typeLabel'),
39629
+ // action-sheet y no el popover default de Ionic: el popover se ancla al
39630
+ // campo, y con el campo cerca del borde inferior y un catálogo largo
39631
+ // Ionic lo recorta arriba Y abajo — quedaban tipos imposibles de elegir.
39632
+ // La hoja usa el alto completo y scrollea sola.
39633
+ selectInterface: 'action-sheet',
39634
+ options: this.allowedTypes().map((type, order) => ({ id: type, name: this.typeLabel(type), order })),
39635
+ };
39636
+ });
39637
+ this.optionsEditorProps = computed(() => {
39638
+ this.i18n.lang();
39639
+ return {
39640
+ title: this.t('optionsTitle'),
39641
+ hint: this.t('optionsHint'),
39642
+ addLabel: this.t('optionsAdd'),
39643
+ optionsLabel: this.t('optionsList'),
39644
+ placeholder: this.t('optionsPlaceholder'),
39645
+ emptyText: this.t('optionsEmpty'),
39646
+ removeOptionLabel: this.t('optionsRemove'),
39647
+ reorderOptionLabel: this.t('optionsReorder'),
39648
+ state: this.state(),
39649
+ options: this.options(),
39650
+ };
39651
+ });
39652
+ if (!this.i18n.hasNamespace(NAMESPACE$2)) {
39653
+ this.i18n.registerDefaults(NAMESPACE$2, FIELD_SCHEMA_EDITOR_I18N);
39654
+ }
39655
+ this.typeControl.valueChanges.subscribe(value => {
39656
+ this.currentType.set(value || 'TEXT');
39657
+ this.emitChange();
39658
+ });
39659
+ this.labelControl.valueChanges.subscribe(value => {
39660
+ if (!this.nameLocked)
39661
+ this.name = this.formSchemas.toToken(value || '');
39662
+ this.emitChange();
39663
+ });
39664
+ this.placeholderControl.valueChanges.subscribe(() => this.emitChange());
39665
+ this.hintControl.valueChanges.subscribe(() => this.emitChange());
39666
+ this.requiredControl.valueChanges.subscribe(() => this.emitChange());
39667
+ }
39668
+ ngOnInit() {
39669
+ const initial = this.props.field;
39670
+ if (initial) {
39671
+ this.fieldId = initial.id;
39672
+ this.nameLocked = true;
39673
+ this.name = initial.name;
39674
+ this.labelControl.setValue(initial.label ?? '');
39675
+ this.placeholderControl.setValue(initial.placeholderText ?? '');
39676
+ this.hintControl.setValue(initial.hintText ?? '');
39677
+ this.requiredControl.setValue(!!initial.required);
39678
+ this.typeControl.setValue(this.coerceType(initial.type));
39679
+ this.options.set(initial.options ?? []);
39680
+ return;
39681
+ }
39682
+ this.fieldId = `field-${++fieldUid}`;
39683
+ this.typeControl.setValue(this.coerceType(this.typeControl.value));
39684
+ const first = this.props.presets?.[0];
39685
+ if (first)
39686
+ this.selectPreset(first);
39687
+ }
39688
+ t(key) {
39689
+ this.i18n.lang();
39690
+ return this.i18n.t(key, NAMESPACE$2);
39691
+ }
39692
+ state() {
39693
+ return this.props.state ?? ComponentStates.ENABLED;
39694
+ }
39695
+ showPresets() {
39696
+ return this.props.mode !== 'edit' && !!this.props.presets?.length;
39697
+ }
39698
+ needsOptions() {
39699
+ return FIELD_TYPES_WITH_OPTIONS.includes(this.currentType());
39700
+ }
39701
+ typeLabel(type) {
39702
+ return this.props.typeLabels?.[type] || this.t(`type${type}`);
39703
+ }
39704
+ setOptions(options) {
39705
+ this.options.set(options);
39706
+ this.emitChange();
39707
+ }
39708
+ emitChange() {
39709
+ // Antes de ngOnInit no hay id: emitir ahí daría un campo sin identidad, que
39710
+ // el consumer no sabría a qué fila aplicar.
39711
+ if (this.fieldId)
39712
+ this.changed.emit(this.value());
39713
+ }
39714
+ selectPreset(preset) {
39715
+ this.selectedPresetId.set(preset.id);
39716
+ const value = preset.field;
39717
+ // El preset fija el token: "correo del comprador" tiene que llamarse
39718
+ // `email` aunque después le cambien el texto visible.
39719
+ this.nameLocked = !!value.name;
39720
+ this.name = value.name || this.formSchemas.toToken(value.label || '');
39721
+ this.labelControl.setValue(value.label ?? '');
39722
+ this.placeholderControl.setValue(value.placeholderText ?? '');
39723
+ this.hintControl.setValue(value.hintText ?? '');
39724
+ this.requiredControl.setValue(!!value.required);
39725
+ this.typeControl.setValue(this.coerceType(value.type));
39726
+ this.options.set(value.options ?? []);
39727
+ this.emitChange();
39728
+ }
39729
+ /**
39730
+ * Valida y emite. Público a propósito: lo llama el consumer desde el botón
39731
+ * de su propia cáscara (modal, página o acordeón).
39732
+ */
39733
+ submit() {
39734
+ const field = this.value();
39735
+ if (!field.label || !field.name || !field.type) {
39736
+ this.validationError.set(this.t('errorIncomplete'));
39737
+ return false;
39738
+ }
39739
+ if (this.needsOptions() && !(field.options?.length ?? 0)) {
39740
+ this.validationError.set(this.t('errorOptionsRequired'));
39741
+ return false;
39742
+ }
39743
+ this.validationError.set('');
39744
+ this.save.emit(field);
39745
+ return true;
39746
+ }
39747
+ /** El campo tal como está en pantalla, sin validar. */
39748
+ value() {
39749
+ const label = (this.labelControl.value || '').trim();
39750
+ return {
39751
+ id: this.fieldId,
39752
+ name: this.formSchemas.toToken(this.name || label),
39753
+ label,
39754
+ placeholderText: (this.placeholderControl.value || '').trim(),
39755
+ hintText: (this.hintControl.value || '').trim(),
39756
+ type: this.coerceType(this.typeControl.value),
39757
+ required: this.requiredControl.value,
39758
+ options: this.needsOptions() ? this.options().map((option, index) => ({ ...option, order: index })) : undefined,
39759
+ };
39760
+ }
39761
+ allowedTypes() {
39762
+ return this.props.allowedTypes?.length ? this.props.allowedTypes : ['TEXT'];
39763
+ }
39764
+ /**
39765
+ * Un tipo fuera del catálogo del consumer cae al primero permitido. Pasa al
39766
+ * editar un campo guardado con un tipo que la app ya no ofrece (bingo lo
39767
+ * hacía con ATTACHMENT cuando el medio de pago no lo admite): sin esto, el
39768
+ * select queda en un valor que no está en su lista y se ve vacío.
39769
+ */
39770
+ coerceType(type) {
39771
+ const allowed = this.allowedTypes();
39772
+ return allowed.includes(type) ? type : allowed[0];
39773
+ }
39774
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FieldSchemaEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
39775
+ 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", changed: "changed" }, ngImport: i0, template: `
39776
+ <div class="field-schema-editor">
39777
+ @if (showPresets()) {
39778
+ <section class="field-schema-editor__presets">
39779
+ <span class="field-schema-editor__section-label">{{ props.presetsTitle || t('presetsTitle') }}</span>
39780
+ <div class="field-schema-editor__preset-grid">
39781
+ @for (preset of props.presets; track preset.id) {
39782
+ <button
39783
+ type="button"
39784
+ class="field-schema-editor__preset"
39785
+ [class.field-schema-editor__preset--selected]="selectedPresetId() === preset.id"
39786
+ (click)="selectPreset(preset)"
39787
+ >
39788
+ <strong>{{ preset.label }}</strong>
39789
+ @if (preset.description) {
39790
+ <span>{{ preset.description }}</span>
39791
+ }
39792
+ </button>
39793
+ }
39794
+ </div>
39795
+ </section>
39796
+ }
39797
+
39798
+ <val-form-field [label]="t('labelLabel')">
39799
+ <val-text-input [props]="{ control: labelControl, placeholder: t('labelPlaceholder'), state: state() }" />
39800
+ </val-form-field>
39801
+
39802
+ <val-form-field [label]="t('typeLabel')">
39803
+ <val-select-input [props]="typeSelectProps()" />
39804
+ </val-form-field>
39805
+
39806
+ @if (props.showPlaceholder !== false) {
39807
+ <val-form-field [label]="t('placeholderLabel')">
39808
+ <val-textarea-input [props]="{ control: placeholderControl, autoGrow: true, state: state() }" />
39809
+ </val-form-field>
39810
+ }
39811
+
39812
+ @if (props.showHint !== false) {
39813
+ <val-form-field [label]="t('hintLabel')">
39814
+ <val-textarea-input [props]="{ control: hintControl, autoGrow: true, state: state() }" />
39815
+ </val-form-field>
39816
+ }
39817
+
39818
+ <val-toggle-input
39819
+ [props]="{
39820
+ control: requiredControl,
39821
+ label: t('requiredLabel'),
39822
+ justify: 'space-between',
39823
+ state: state(),
39824
+ }"
39825
+ />
39826
+
39827
+ @if (needsOptions()) {
39828
+ <val-field-options-editor [props]="optionsEditorProps()" (optionsChange)="setOptions($event)" />
39829
+ }
39830
+
39831
+ @if (validationError()) {
39832
+ <p class="field-schema-editor__error">{{ validationError() }}</p>
39833
+ }
39834
+ </div>
39835
+ `, 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"] }] }); }
39836
+ }
39837
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FieldSchemaEditorComponent, decorators: [{
39838
+ type: Component,
39839
+ args: [{ selector: 'val-field-schema-editor', standalone: true, imports: [
39840
+ CommonModule,
39841
+ ReactiveFormsModule,
39842
+ FormFieldComponent,
39843
+ TextInputComponent,
39844
+ TextareaInputComponent,
39845
+ SearchSelectorComponent,
39846
+ ToggleInputComponent,
39847
+ FieldOptionsEditorComponent,
39848
+ ], template: `
39849
+ <div class="field-schema-editor">
39850
+ @if (showPresets()) {
39851
+ <section class="field-schema-editor__presets">
39852
+ <span class="field-schema-editor__section-label">{{ props.presetsTitle || t('presetsTitle') }}</span>
39853
+ <div class="field-schema-editor__preset-grid">
39854
+ @for (preset of props.presets; track preset.id) {
39855
+ <button
39856
+ type="button"
39857
+ class="field-schema-editor__preset"
39858
+ [class.field-schema-editor__preset--selected]="selectedPresetId() === preset.id"
39859
+ (click)="selectPreset(preset)"
39860
+ >
39861
+ <strong>{{ preset.label }}</strong>
39862
+ @if (preset.description) {
39863
+ <span>{{ preset.description }}</span>
39864
+ }
39865
+ </button>
39866
+ }
39867
+ </div>
39868
+ </section>
39869
+ }
39870
+
39871
+ <val-form-field [label]="t('labelLabel')">
39872
+ <val-text-input [props]="{ control: labelControl, placeholder: t('labelPlaceholder'), state: state() }" />
39873
+ </val-form-field>
39874
+
39875
+ <val-form-field [label]="t('typeLabel')">
39876
+ <val-select-input [props]="typeSelectProps()" />
39877
+ </val-form-field>
39878
+
39879
+ @if (props.showPlaceholder !== false) {
39880
+ <val-form-field [label]="t('placeholderLabel')">
39881
+ <val-textarea-input [props]="{ control: placeholderControl, autoGrow: true, state: state() }" />
39882
+ </val-form-field>
39883
+ }
39884
+
39885
+ @if (props.showHint !== false) {
39886
+ <val-form-field [label]="t('hintLabel')">
39887
+ <val-textarea-input [props]="{ control: hintControl, autoGrow: true, state: state() }" />
39888
+ </val-form-field>
39889
+ }
39890
+
39891
+ <val-toggle-input
39892
+ [props]="{
39893
+ control: requiredControl,
39894
+ label: t('requiredLabel'),
39895
+ justify: 'space-between',
39896
+ state: state(),
39897
+ }"
39898
+ />
39899
+
39900
+ @if (needsOptions()) {
39901
+ <val-field-options-editor [props]="optionsEditorProps()" (optionsChange)="setOptions($event)" />
39902
+ }
39903
+
39904
+ @if (validationError()) {
39905
+ <p class="field-schema-editor__error">{{ validationError() }}</p>
39906
+ }
39907
+ </div>
39908
+ `, 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"] }]
39909
+ }], ctorParameters: () => [], propDecorators: { props: [{
39910
+ type: Input
39911
+ }], save: [{
39912
+ type: Output
39913
+ }], changed: [{
39914
+ type: Output
39915
+ }] } });
39916
+
39173
39917
  class RequestFormBuilderService extends FormSchemaBuilderService {
39174
39918
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestFormBuilderService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
39175
39919
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestFormBuilderService, providedIn: 'root' }); }
@@ -39179,6 +39923,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
39179
39923
  args: [{ providedIn: 'root' }]
39180
39924
  }] });
39181
39925
 
39926
+ /**
39927
+ * Tipos de pregunta que ofrece una encuesta.
39928
+ *
39929
+ * El corte no es estético: **todo tipo que esta lista ofrezca, `val-survey-response`
39930
+ * tiene que saber renderizarlo**, o la encuesta se guarda bien y se responde mal
39931
+ * (un rating dibujado como caja de texto). Ampliar acá obliga a ampliar el
39932
+ * renderer en la misma entrega.
39933
+ *
39934
+ * Los seis primeros son de respuesta cerrada — `closedAnswerTypes` en
39935
+ * `backend/go/services/request/aggregate.go` los cuenta por valor en el
39936
+ * dashboard. Los seis últimos son abiertos: el dashboard lista sus respuestas.
39937
+ *
39938
+ * `FILE`/`ATTACHMENT` quedan deliberadamente fuera: una encuesta es pública y
39939
+ * anónima por default, y aceptar subidas ahí es superficie de abuso sin un caso
39940
+ * de uso que lo pida.
39941
+ */
39942
+ const SURVEY_QUESTION_TYPES = [
39943
+ 'EMOJI_RATING',
39944
+ 'SELECT',
39945
+ 'RADIO',
39946
+ 'MULTI_SELECT',
39947
+ 'CHECK',
39948
+ 'TOGGLE',
39949
+ 'TEXT',
39950
+ 'TEXTAREA',
39951
+ 'NUMBER',
39952
+ 'DATE',
39953
+ 'EMAIL',
39954
+ 'PHONE',
39955
+ ];
39956
+
39182
39957
  const SURVEY_BUILDER_I18N = {
39183
39958
  es: {
39184
39959
  titleLabel: 'Título',
@@ -39187,10 +39962,16 @@ const SURVEY_BUILDER_I18N = {
39187
39962
  subtitlePlaceholder: 'Ej: Ayúdanos a mejorar respondiendo estas preguntas',
39188
39963
  questionsLabel: 'Preguntas',
39189
39964
  questionLabelPlaceholder: 'Escribe la pregunta',
39190
- typeEmoji: 'Emoji',
39191
- typeText: 'Texto libre',
39192
39965
  addQuestion: 'Agregar pregunta',
39193
- removeQuestion: 'Quitar',
39966
+ removeQuestion: 'Quitar pregunta',
39967
+ editQuestion: 'Editar pregunta',
39968
+ duplicateQuestion: 'Duplicar pregunta',
39969
+ moveUp: 'Subir pregunta',
39970
+ moveDown: 'Bajar pregunta',
39971
+ doneEditing: 'Listo',
39972
+ untitledQuestion: 'Pregunta sin texto',
39973
+ requiredTag: 'Obligatoria',
39974
+ missingOptions: 'Hay preguntas de opciones sin ninguna opción cargada.',
39194
39975
  save: 'Guardar encuesta',
39195
39976
  noTitle: 'Ponle un título a la encuesta',
39196
39977
  noQuestions: 'Agrega al menos una pregunta',
@@ -39205,10 +39986,16 @@ const SURVEY_BUILDER_I18N = {
39205
39986
  subtitlePlaceholder: 'E.g: Help us improve by answering these questions',
39206
39987
  questionsLabel: 'Questions',
39207
39988
  questionLabelPlaceholder: 'Write the question',
39208
- typeEmoji: 'Emoji',
39209
- typeText: 'Free text',
39210
39989
  addQuestion: 'Add question',
39211
- removeQuestion: 'Remove',
39990
+ removeQuestion: 'Remove question',
39991
+ editQuestion: 'Edit question',
39992
+ duplicateQuestion: 'Duplicate question',
39993
+ moveUp: 'Move question up',
39994
+ moveDown: 'Move question down',
39995
+ doneEditing: 'Done',
39996
+ untitledQuestion: 'Question with no text',
39997
+ requiredTag: 'Required',
39998
+ missingOptions: 'Some choice questions have no options set.',
39212
39999
  save: 'Save survey',
39213
40000
  noTitle: 'Give the survey a title',
39214
40001
  noQuestions: 'Add at least one question',
@@ -39219,6 +40006,7 @@ const SURVEY_BUILDER_I18N = {
39219
40006
  };
39220
40007
 
39221
40008
  const NAMESPACE$1 = 'SurveyBuilder';
40009
+ const EDITOR_NAMESPACE = 'FieldSchemaEditor';
39222
40010
  const DEFAULT_TYPE_PREFIX = 'survey';
39223
40011
  let rowUid = 0;
39224
40012
  /**
@@ -39229,11 +40017,14 @@ let rowUid = 0;
39229
40017
  * true` (cada respuesta se agrega, no se revisa una por una) y el `EntityRef`
39230
40018
  * que le pasa el consumer (ej. el evento de bingo dueño de la encuesta).
39231
40019
  *
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.
40020
+ * Cada pregunta se edita con `val-field-schema-editor`, el mismo editor que usa
40021
+ * el checkout de bingo de ahí que una pregunta tenga tipo, placeholder, nota
40022
+ * y opciones, no solo texto. La v1 de este componente tenía su propio editor
40023
+ * pobre (dos tipos, sin opciones); la Fase 5 del ADR-092 lo reemplazó por el
40024
+ * organism compartido en vez de mejorarlo por duplicado.
40025
+ *
40026
+ * El catálogo que ofrece sale de `SURVEY_QUESTION_TYPES` — acotado a lo que
40027
+ * `val-survey-response` sabe renderizar, no a lo que el editor sabe editar.
39237
40028
  */
39238
40029
  class SurveyBuilderComponent {
39239
40030
  constructor() {
@@ -39243,15 +40034,21 @@ class SurveyBuilderComponent {
39243
40034
  this.requests = inject(RequestService);
39244
40035
  this.formBuilder = inject(RequestFormBuilderService);
39245
40036
  this.errors = inject(ValtechErrorService);
39246
- this.emojiType = FIELD_TYPE_EMOJI_RATING;
39247
40037
  this.titleControl = new FormControl('', { nonNullable: true });
39248
40038
  this.subtitleControl = new FormControl('', { nonNullable: true });
39249
40039
  this.questions = signal([]);
40040
+ this.editingId = signal('');
40041
+ this.validationError = signal('');
39250
40042
  this.saving = signal(false);
39251
- this.rowControls = new Map();
39252
40043
  if (!this.i18n.hasNamespace(NAMESPACE$1)) {
39253
40044
  this.i18n.registerDefaults(NAMESPACE$1, SURVEY_BUILDER_I18N);
39254
40045
  }
40046
+ // El nombre del tipo de cada pregunta se muestra en la fila colapsada, que
40047
+ // dibuja este componente y no el editor — si nadie montó un editor todavía,
40048
+ // el namespace no existiría y la fila mostraría la clave cruda.
40049
+ if (!this.i18n.hasNamespace(EDITOR_NAMESPACE)) {
40050
+ this.i18n.registerDefaults(EDITOR_NAMESPACE, FIELD_SCHEMA_EDITOR_I18N);
40051
+ }
39255
40052
  }
39256
40053
  async ngOnInit() {
39257
40054
  if (!this.props.typeId)
@@ -39270,53 +40067,143 @@ class SurveyBuilderComponent {
39270
40067
  });
39271
40068
  }
39272
40069
  }
39273
- rowFromField(f) {
39274
- const id = `row-${rowUid++}`;
39275
- this.rowControls.set(id, new FormControl(f.label ?? '', { nonNullable: true }));
40070
+ t(key) {
40071
+ this.i18n.lang();
40072
+ return this.i18n.t(key, NAMESPACE$1);
40073
+ }
40074
+ typeLabel(type) {
40075
+ this.i18n.lang();
40076
+ return this.i18n.t(`type${type}`, EDITOR_NAMESPACE);
40077
+ }
40078
+ questionTypes() {
40079
+ return this.props.questionTypes?.length ? this.props.questionTypes : SURVEY_QUESTION_TYPES;
40080
+ }
40081
+ editorProps(row) {
40082
+ return { mode: 'edit', field: row, allowedTypes: this.questionTypes() };
40083
+ }
40084
+ iconAction(token, icon, label, disabled = false, color = 'dark') {
39276
40085
  return {
39277
- id,
39278
- label: f.label ?? '',
39279
- type: f.type === FIELD_TYPE_EMOJI_RATING ? 'EMOJI_RATING' : 'TEXTAREA',
39280
- required: !!f.required,
40086
+ token,
40087
+ text: '',
40088
+ ariaLabel: label,
40089
+ color,
40090
+ fill: 'clear',
40091
+ shape: 'round',
40092
+ size: 'small',
40093
+ type: 'button',
40094
+ state: disabled ? ComponentStates.DISABLED : ComponentStates.ENABLED,
40095
+ icon: { name: icon, slot: 'icon-only' },
39281
40096
  };
39282
40097
  }
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
40098
  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 }]);
40099
+ const row = {
40100
+ id: `row-${rowUid++}`,
40101
+ name: '',
40102
+ label: '',
40103
+ placeholderText: '',
40104
+ hintText: '',
40105
+ type: this.questionTypes()[0],
40106
+ required: true,
40107
+ };
40108
+ this.questions.update(rows => [...rows, row]);
40109
+ // Una pregunta recién agregada está vacía: abrirla es el único paso
40110
+ // siguiente posible, y dejarla cerrada la haría ver como una fila rota.
40111
+ this.editingId.set(row.id);
40112
+ }
40113
+ toggleEdit(row) {
40114
+ this.editingId.update(current => (current === row.id ? '' : row.id));
39295
40115
  }
39296
40116
  removeQuestion(row) {
39297
- this.rowControls.delete(row.id);
39298
40117
  this.questions.update(rows => rows.filter(r => r.id !== row.id));
40118
+ if (this.editingId() === row.id)
40119
+ this.editingId.set('');
40120
+ }
40121
+ duplicate(row) {
40122
+ const copy = {
40123
+ ...row,
40124
+ id: `row-${rowUid++}`,
40125
+ // Sin token propio la copia pisaría las respuestas del original: dos
40126
+ // campos con el mismo `name` son el mismo campo para el backend.
40127
+ name: this.formBuilder.uniqueFieldName(row.name || row.label, this.questions()),
40128
+ options: row.options ? row.options.map(o => ({ ...o })) : undefined,
40129
+ };
40130
+ this.questions.update(rows => {
40131
+ const index = rows.findIndex(r => r.id === row.id);
40132
+ return [...rows.slice(0, index + 1), copy, ...rows.slice(index + 1)];
40133
+ });
40134
+ }
40135
+ move(index, delta) {
40136
+ const target = index + delta;
40137
+ this.questions.update(rows => {
40138
+ if (target < 0 || target >= rows.length)
40139
+ return rows;
40140
+ const next = [...rows];
40141
+ [next[index], next[target]] = [next[target], next[index]];
40142
+ return next;
40143
+ });
39299
40144
  }
39300
- setType(row, type) {
39301
- this.questions.update(rows => rows.map(r => (r.id === row.id ? { ...r, type } : r)));
40145
+ /**
40146
+ * Guarda lo que se está escribiendo, sin cerrar el editor ni validar.
40147
+ *
40148
+ * Sin esto, lo tipeado solo existía dentro del editor hasta que alguien
40149
+ * tocaba "Listo": la fila seguía diciendo "Pregunta sin texto" y, peor,
40150
+ * "Guardar encuesta" fallaba con "todas las preguntas necesitan texto" sobre
40151
+ * una pregunta que en pantalla claramente tenía texto. Tocar "Listo" era un
40152
+ * paso obligatorio que no se veía por ningún lado.
40153
+ */
40154
+ draftQuestion(field) {
40155
+ this.questions.update(rows => rows.map(r => (r.id === field.id ? { ...r, ...field } : r)));
40156
+ if (this.validationError())
40157
+ this.validationError.set('');
40158
+ }
40159
+ applyQuestion(field) {
40160
+ this.questions.update(rows => rows.map(r => r.id === field.id
40161
+ ? {
40162
+ ...field,
40163
+ name: this.formBuilder.uniqueFieldName(field.name || field.label, rows.filter(o => o.id !== r.id)),
40164
+ }
40165
+ : r));
40166
+ this.editingId.set('');
40167
+ this.validationError.set('');
39302
40168
  }
39303
40169
  async save() {
39304
40170
  if (this.saving())
39305
40171
  return;
40172
+ this.validationError.set('');
39306
40173
  const title = this.titleControl.value.trim();
39307
40174
  if (!title) {
39308
40175
  this.titleControl.markAsTouched();
40176
+ this.validationError.set(this.t('noTitle'));
39309
40177
  return;
39310
40178
  }
39311
40179
  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))
40180
+ if (!rows.length) {
40181
+ this.validationError.set(this.t('noQuestions'));
40182
+ return;
40183
+ }
40184
+ if (rows.some(r => !r.label.trim())) {
40185
+ this.validationError.set(this.t('emptyQuestionLabel'));
40186
+ return;
40187
+ }
40188
+ // El editor ya lo valida al cerrar cada pregunta, pero una pregunta abierta
40189
+ // (o cargada de una encuesta vieja) puede llegar acá sin opciones.
40190
+ if (rows.some(r => FIELD_TYPES_WITH_OPTIONS.includes(r.type) && !r.options?.length)) {
40191
+ this.validationError.set(this.t('missingOptions'));
39314
40192
  return;
40193
+ }
39315
40194
  this.saving.set(true);
39316
40195
  const fieldSchema = [];
39317
40196
  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));
40197
+ const name = this.formBuilder.uniqueFieldName(row.name || row.label, fieldSchema);
40198
+ fieldSchema.push(this.formBuilder.createFieldSchema({
40199
+ name,
40200
+ label: row.label.trim(),
40201
+ type: row.type,
40202
+ required: row.required,
40203
+ placeholderText: row.placeholderText,
40204
+ hintText: row.hintText,
40205
+ options: row.options,
40206
+ }, index));
39320
40207
  });
39321
40208
  const typeId = this.props.typeId ?? `${this.props.typePrefix ?? DEFAULT_TYPE_PREFIX}:${this.formBuilder.toToken(title)}`;
39322
40209
  const subtitle = this.subtitleControl.value.trim();
@@ -39346,68 +40233,89 @@ class SurveyBuilderComponent {
39346
40233
  });
39347
40234
  }
39348
40235
  }
39349
- t(key) {
39350
- return this.i18n.t(key, NAMESPACE$1);
40236
+ rowFromField(f) {
40237
+ return {
40238
+ id: `row-${rowUid++}`,
40239
+ name: f.name,
40240
+ label: f.label ?? '',
40241
+ placeholderText: f.placeholderText ?? '',
40242
+ hintText: f.hintText ?? '',
40243
+ type: f.type,
40244
+ required: !!f.required,
40245
+ options: f.options,
40246
+ };
39351
40247
  }
39352
40248
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
39353
40249
  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
40250
  <div class="survey-builder">
39355
- <div class="survey-builder__field">
39356
- <val-title [props]="{ content: t('titleLabel'), size: 'small', color: 'dark', bold: false }" />
40251
+ <val-form-field [label]="t('titleLabel')">
39357
40252
  <val-text-input [props]="{ control: titleControl, placeholder: t('titlePlaceholder') }" />
39358
- </div>
39359
- <div class="survey-builder__field">
39360
- <val-title [props]="{ content: t('subtitleLabel'), size: 'small', color: 'dark', bold: false }" />
40253
+ </val-form-field>
40254
+ <val-form-field [label]="t('subtitleLabel')">
39361
40255
  <val-text-input [props]="{ control: subtitleControl, placeholder: t('subtitlePlaceholder') }" />
39362
- </div>
40256
+ </val-form-field>
39363
40257
 
39364
40258
  <div class="survey-builder__questions">
39365
40259
  <span class="survey-builder__questions-label">{{ t('questionsLabel') }}</span>
39366
40260
 
39367
- @for (row of questions(); track row.id) {
40261
+ @if (!questions().length) {
40262
+ <p class="survey-builder__empty">{{ t('noQuestions') }}</p>
40263
+ }
40264
+
40265
+ @for (row of questions(); track row.id; let i = $index; let first = $first; let last = $last) {
39368
40266
  <div class="survey-builder__row">
39369
- <val-text-input [props]="{ control: rowLabelControl(row), placeholder: t('questionLabelPlaceholder') }" />
39370
- <div class="survey-builder__row-actions">
39371
- <val-button
39372
- [props]="{
39373
- token: 'q-type-emoji-' + row.id,
39374
- text: t('typeEmoji'),
39375
- color: row.type === emojiType ? 'primary' : 'medium',
39376
- fill: row.type === emojiType ? 'solid' : 'outline',
39377
- shape: 'round',
39378
- size: 'small',
39379
- type: 'button',
39380
- state: 'ENABLED',
39381
- }"
39382
- (onClick)="setType(row, emojiType)"
39383
- />
39384
- <val-button
39385
- [props]="{
39386
- token: 'q-type-text-' + row.id,
39387
- text: t('typeText'),
39388
- color: row.type === 'TEXTAREA' ? 'primary' : 'medium',
39389
- fill: row.type === 'TEXTAREA' ? 'solid' : 'outline',
39390
- shape: 'round',
39391
- size: 'small',
39392
- type: 'button',
39393
- state: 'ENABLED',
39394
- }"
39395
- (onClick)="setType(row, 'TEXTAREA')"
39396
- />
39397
- <val-button
39398
- [props]="{
39399
- token: 'q-remove-' + row.id,
39400
- text: t('removeQuestion'),
39401
- color: 'danger',
39402
- fill: 'clear',
39403
- shape: 'round',
39404
- size: 'small',
39405
- type: 'button',
39406
- state: 'ENABLED',
39407
- }"
39408
- (onClick)="removeQuestion(row)"
39409
- />
40267
+ <div class="survey-builder__row-head">
40268
+ <div class="survey-builder__row-text">
40269
+ <strong>{{ row.label || t('untitledQuestion') }}</strong>
40270
+ <span>{{ typeLabel(row.type) }}{{ row.required ? ' · ' + t('requiredTag') : '' }}</span>
40271
+ </div>
40272
+ <div class="survey-builder__row-actions">
40273
+ <val-button
40274
+ [props]="iconAction('q-up-' + row.id, 'arrow-up-outline', t('moveUp'), first)"
40275
+ (onClick)="move(i, -1)"
40276
+ />
40277
+ <val-button
40278
+ [props]="iconAction('q-down-' + row.id, 'arrow-down-outline', t('moveDown'), last)"
40279
+ (onClick)="move(i, 1)"
40280
+ />
40281
+ <val-button
40282
+ [props]="iconAction('q-copy-' + row.id, 'copy-outline', t('duplicateQuestion'))"
40283
+ (onClick)="duplicate(row)"
40284
+ />
40285
+ <val-button
40286
+ [props]="iconAction('q-edit-' + row.id, 'create-outline', t('editQuestion'))"
40287
+ (onClick)="toggleEdit(row)"
40288
+ />
40289
+ <val-button
40290
+ [props]="iconAction('q-remove-' + row.id, 'trash-outline', t('removeQuestion'), false, 'danger')"
40291
+ (onClick)="removeQuestion(row)"
40292
+ />
40293
+ </div>
39410
40294
  </div>
40295
+
40296
+ @if (editingId() === row.id) {
40297
+ <div class="survey-builder__row-editor">
40298
+ <val-field-schema-editor
40299
+ #editor
40300
+ [props]="editorProps(row)"
40301
+ (changed)="draftQuestion($event)"
40302
+ (save)="applyQuestion($event)"
40303
+ />
40304
+ <val-button
40305
+ [props]="{
40306
+ token: 'q-done-' + row.id,
40307
+ text: t('doneEditing'),
40308
+ color: 'dark',
40309
+ fill: 'solid',
40310
+ shape: 'round',
40311
+ size: 'default',
40312
+ type: 'button',
40313
+ state: 'ENABLED',
40314
+ }"
40315
+ (onClick)="editor.submit()"
40316
+ />
40317
+ </div>
40318
+ }
39411
40319
  </div>
39412
40320
  }
39413
40321
 
@@ -39427,6 +40335,10 @@ class SurveyBuilderComponent {
39427
40335
  />
39428
40336
  </div>
39429
40337
 
40338
+ @if (validationError()) {
40339
+ <p class="survey-builder__error">{{ validationError() }}</p>
40340
+ }
40341
+
39430
40342
  <val-button
39431
40343
  [props]="{
39432
40344
  token: 'survey-builder-save',
@@ -39434,6 +40346,7 @@ class SurveyBuilderComponent {
39434
40346
  color: 'dark',
39435
40347
  fill: 'solid',
39436
40348
  shape: 'round',
40349
+ size: 'large',
39437
40350
  expand: 'block',
39438
40351
  type: 'button',
39439
40352
  state: saving() ? 'WORKING' : 'ENABLED',
@@ -39441,68 +40354,87 @@ class SurveyBuilderComponent {
39441
40354
  (onClick)="save()"
39442
40355
  />
39443
40356
  </div>
39444
- `, isInline: true, styles: [":host{display:block}.survey-builder{display:flex;flex-direction:column;gap:16px}.survey-builder__field{display:flex;flex-direction:column;gap:6px}.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"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
40357
+ `, 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", "changed"] }] }); }
39445
40358
  }
39446
40359
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SurveyBuilderComponent, decorators: [{
39447
40360
  type: Component,
39448
- args: [{ selector: 'val-survey-builder', standalone: true, imports: [CommonModule, ReactiveFormsModule, TextInputComponent, ButtonComponent, TitleComponent], template: `
40361
+ args: [{ selector: 'val-survey-builder', standalone: true, imports: [
40362
+ CommonModule,
40363
+ ReactiveFormsModule,
40364
+ TextInputComponent,
40365
+ ButtonComponent,
40366
+ FormFieldComponent,
40367
+ FieldSchemaEditorComponent,
40368
+ ], template: `
39449
40369
  <div class="survey-builder">
39450
- <div class="survey-builder__field">
39451
- <val-title [props]="{ content: t('titleLabel'), size: 'small', color: 'dark', bold: false }" />
40370
+ <val-form-field [label]="t('titleLabel')">
39452
40371
  <val-text-input [props]="{ control: titleControl, placeholder: t('titlePlaceholder') }" />
39453
- </div>
39454
- <div class="survey-builder__field">
39455
- <val-title [props]="{ content: t('subtitleLabel'), size: 'small', color: 'dark', bold: false }" />
40372
+ </val-form-field>
40373
+ <val-form-field [label]="t('subtitleLabel')">
39456
40374
  <val-text-input [props]="{ control: subtitleControl, placeholder: t('subtitlePlaceholder') }" />
39457
- </div>
40375
+ </val-form-field>
39458
40376
 
39459
40377
  <div class="survey-builder__questions">
39460
40378
  <span class="survey-builder__questions-label">{{ t('questionsLabel') }}</span>
39461
40379
 
39462
- @for (row of questions(); track row.id) {
40380
+ @if (!questions().length) {
40381
+ <p class="survey-builder__empty">{{ t('noQuestions') }}</p>
40382
+ }
40383
+
40384
+ @for (row of questions(); track row.id; let i = $index; let first = $first; let last = $last) {
39463
40385
  <div class="survey-builder__row">
39464
- <val-text-input [props]="{ control: rowLabelControl(row), placeholder: t('questionLabelPlaceholder') }" />
39465
- <div class="survey-builder__row-actions">
39466
- <val-button
39467
- [props]="{
39468
- token: 'q-type-emoji-' + row.id,
39469
- text: t('typeEmoji'),
39470
- color: row.type === emojiType ? 'primary' : 'medium',
39471
- fill: row.type === emojiType ? 'solid' : 'outline',
39472
- shape: 'round',
39473
- size: 'small',
39474
- type: 'button',
39475
- state: 'ENABLED',
39476
- }"
39477
- (onClick)="setType(row, emojiType)"
39478
- />
39479
- <val-button
39480
- [props]="{
39481
- token: 'q-type-text-' + row.id,
39482
- text: t('typeText'),
39483
- color: row.type === 'TEXTAREA' ? 'primary' : 'medium',
39484
- fill: row.type === 'TEXTAREA' ? 'solid' : 'outline',
39485
- shape: 'round',
39486
- size: 'small',
39487
- type: 'button',
39488
- state: 'ENABLED',
39489
- }"
39490
- (onClick)="setType(row, 'TEXTAREA')"
39491
- />
39492
- <val-button
39493
- [props]="{
39494
- token: 'q-remove-' + row.id,
39495
- text: t('removeQuestion'),
39496
- color: 'danger',
39497
- fill: 'clear',
39498
- shape: 'round',
39499
- size: 'small',
39500
- type: 'button',
39501
- state: 'ENABLED',
39502
- }"
39503
- (onClick)="removeQuestion(row)"
39504
- />
40386
+ <div class="survey-builder__row-head">
40387
+ <div class="survey-builder__row-text">
40388
+ <strong>{{ row.label || t('untitledQuestion') }}</strong>
40389
+ <span>{{ typeLabel(row.type) }}{{ row.required ? ' · ' + t('requiredTag') : '' }}</span>
40390
+ </div>
40391
+ <div class="survey-builder__row-actions">
40392
+ <val-button
40393
+ [props]="iconAction('q-up-' + row.id, 'arrow-up-outline', t('moveUp'), first)"
40394
+ (onClick)="move(i, -1)"
40395
+ />
40396
+ <val-button
40397
+ [props]="iconAction('q-down-' + row.id, 'arrow-down-outline', t('moveDown'), last)"
40398
+ (onClick)="move(i, 1)"
40399
+ />
40400
+ <val-button
40401
+ [props]="iconAction('q-copy-' + row.id, 'copy-outline', t('duplicateQuestion'))"
40402
+ (onClick)="duplicate(row)"
40403
+ />
40404
+ <val-button
40405
+ [props]="iconAction('q-edit-' + row.id, 'create-outline', t('editQuestion'))"
40406
+ (onClick)="toggleEdit(row)"
40407
+ />
40408
+ <val-button
40409
+ [props]="iconAction('q-remove-' + row.id, 'trash-outline', t('removeQuestion'), false, 'danger')"
40410
+ (onClick)="removeQuestion(row)"
40411
+ />
40412
+ </div>
39505
40413
  </div>
40414
+
40415
+ @if (editingId() === row.id) {
40416
+ <div class="survey-builder__row-editor">
40417
+ <val-field-schema-editor
40418
+ #editor
40419
+ [props]="editorProps(row)"
40420
+ (changed)="draftQuestion($event)"
40421
+ (save)="applyQuestion($event)"
40422
+ />
40423
+ <val-button
40424
+ [props]="{
40425
+ token: 'q-done-' + row.id,
40426
+ text: t('doneEditing'),
40427
+ color: 'dark',
40428
+ fill: 'solid',
40429
+ shape: 'round',
40430
+ size: 'default',
40431
+ type: 'button',
40432
+ state: 'ENABLED',
40433
+ }"
40434
+ (onClick)="editor.submit()"
40435
+ />
40436
+ </div>
40437
+ }
39506
40438
  </div>
39507
40439
  }
39508
40440
 
@@ -39522,6 +40454,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
39522
40454
  />
39523
40455
  </div>
39524
40456
 
40457
+ @if (validationError()) {
40458
+ <p class="survey-builder__error">{{ validationError() }}</p>
40459
+ }
40460
+
39525
40461
  <val-button
39526
40462
  [props]="{
39527
40463
  token: 'survey-builder-save',
@@ -39529,6 +40465,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
39529
40465
  color: 'dark',
39530
40466
  fill: 'solid',
39531
40467
  shape: 'round',
40468
+ size: 'large',
39532
40469
  expand: 'block',
39533
40470
  type: 'button',
39534
40471
  state: saving() ? 'WORKING' : 'ENABLED',
@@ -39536,7 +40473,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
39536
40473
  (onClick)="save()"
39537
40474
  />
39538
40475
  </div>
39539
- `, styles: [":host{display:block}.survey-builder{display:flex;flex-direction:column;gap:16px}.survey-builder__field{display:flex;flex-direction:column;gap:6px}.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"] }]
40476
+ `, 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"] }]
39540
40477
  }], ctorParameters: () => [], propDecorators: { props: [{
39541
40478
  type: Input
39542
40479
  }], saved: [{
@@ -89158,66 +90095,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
89158
90095
  * aporta la sesión de escaneo, el feedback y el historial.
89159
90096
  */
89160
90097
 
89161
- /**
89162
- * Primitivo de campo de formulario: label (estilo val-form) + contenido proyectado.
89163
- *
89164
- * Usar cuando necesitás un campo fuera de val-form pero con el mismo estilo:
89165
- * image-picker, date-picker, selectores custom, chips, etc.
89166
- *
89167
- * NOTA: para ion-input / ion-textarea NO usar ng-content projection — el Shadow DOM
89168
- * de Ionic no responde al grid del host. Usá en cambio un `<div class="pf-field">`
89169
- * plano en el template con `<p class="pf-label">` + ion-input directo.
89170
- *
89171
- * ```html
89172
- * <val-form-field label="Imagen">
89173
- * <app-image-picker ... />
89174
- * </val-form-field>
89175
- *
89176
- * <!-- Para ion-input: NO val-form-field, usar div plano -->
89177
- * <div class="pf-field">
89178
- * <p class="pf-label">Nombre</p>
89179
- * <ion-input fill="outline" ... />
89180
- * </div>
89181
- * ```
89182
- *
89183
- * El label usa el mismo val-title que val-form (size=small, color=dark, bold=false).
89184
- * El spacing entre campos se controla con --val-form-field-gap (default 0.5rem).
89185
- * El padding horizontal se hereda vía --val-form-field-padding (default 0).
89186
- * Setearlo en el contenedor padre para consistencia sin override en cada campo:
89187
- * `.my-card { --val-form-field-padding: 0 16px; }`
89188
- */
89189
- class FormFieldComponent {
89190
- constructor() {
89191
- this.label = input('');
89192
- this.titleProps = computed(() => ({
89193
- content: this.label(),
89194
- size: 'small',
89195
- color: 'dark',
89196
- bold: false,
89197
- }));
89198
- }
89199
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
89200
- 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: `
89201
- @if (label()) {
89202
- <div class="vff-label">
89203
- <val-title [props]="titleProps()" />
89204
- </div>
89205
- }
89206
- <ng-content />
89207
- `, 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"] }] }); }
89208
- }
89209
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FormFieldComponent, decorators: [{
89210
- type: Component,
89211
- args: [{ selector: 'val-form-field', standalone: true, imports: [TitleComponent], template: `
89212
- @if (label()) {
89213
- <div class="vff-label">
89214
- <val-title [props]="titleProps()" />
89215
- </div>
89216
- }
89217
- <ng-content />
89218
- `, 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"] }]
89219
- }] });
89220
-
89221
90098
  /**
89222
90099
  * Token de inyeccion para la configuracion del Chat.
89223
90100
  */
@@ -92031,5 +92908,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
92031
92908
  * Generated bundle index. Do not edit.
92032
92909
  */
92033
92910
 
92034
- 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 };
92911
+ 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 };
92035
92912
  //# sourceMappingURL=valtech-components.mjs.map