valtech-components 4.0.237 → 4.0.239

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -56,7 +56,7 @@ import { BrowserMultiFormatReader } from '@zxing/browser';
56
56
  * Current version of valtech-components.
57
57
  * This is automatically updated during the publish process.
58
58
  */
59
- const VERSION = '4.0.237';
59
+ const VERSION = '4.0.239';
60
60
 
61
61
  // Control de estado de refresco (singleton a nivel de módulo)
62
62
  let isRefreshing = false;
@@ -30066,7 +30066,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
30066
30066
  type: Output
30067
30067
  }] } });
30068
30068
 
30069
- const DEFAULT_NAMESPACE$l = 'PermissionSelector';
30069
+ const DEFAULT_NAMESPACE$m = 'PermissionSelector';
30070
30070
  const PERMISSION_SELECTOR_I18N = {
30071
30071
  es: {
30072
30072
  selectAll: 'Todo',
@@ -30135,7 +30135,7 @@ class PermissionSelectorComponent {
30135
30135
  };
30136
30136
  });
30137
30137
  });
30138
- this.i18n.registerDefaults(DEFAULT_NAMESPACE$l, PERMISSION_SELECTOR_I18N);
30138
+ this.i18n.registerDefaults(DEFAULT_NAMESPACE$m, PERMISSION_SELECTOR_I18N);
30139
30139
  }
30140
30140
  toggleAction(code) {
30141
30141
  const sel = new Set(this.selected());
@@ -30161,7 +30161,7 @@ class PermissionSelectorComponent {
30161
30161
  }
30162
30162
  tt(key) {
30163
30163
  this.i18n.lang();
30164
- return this.i18n.t(key, DEFAULT_NAMESPACE$l);
30164
+ return this.i18n.t(key, DEFAULT_NAMESPACE$m);
30165
30165
  }
30166
30166
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
30167
30167
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: PermissionSelectorComponent, isStandalone: true, selector: "val-permission-selector", inputs: { permissions: { classPropertyName: "permissions", publicName: "permissions", isSignal: true, isRequired: false, transformFunction: null }, actionLabels: { classPropertyName: "actionLabels", publicName: "actionLabels", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange" }, ngImport: i0, template: `
@@ -45685,43 +45685,27 @@ const PROFILE_VIEW_I18N = {
45685
45685
  },
45686
45686
  };
45687
45687
 
45688
- /** Patrón sincronizado con backend handleRegex: lowercase alfanum + underscore. */
45689
45688
  const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
45690
45689
  const HANDLE_MIN = 3;
45691
45690
  const HANDLE_MAX = 20;
45692
- const DEFAULT_NAMESPACE$k = 'Settings.Profile';
45691
+ const DEFAULT_NAMESPACE$l = 'Settings.Profile';
45693
45692
  /**
45694
- * `val-profile-view` — vista Perfil full-feature autocontenida (organism).
45695
- *
45696
- * Edita los campos públicos del usuario en UN solo formulario con UN solo botón
45697
- * "Guardar". Wireup:
45698
- * - Avatar — val-avatar-upload (Firebase Storage + backend sync). Separado del
45699
- * form porque es upload sync independiente.
45700
- * - Handle (@usuario) — InputType.HANDLE (val-username-input con check de
45701
- * disponibilidad async resuelto vía AuthService inyectado).
45702
- * - Nombre — InputType.TEXT.
45703
- * - Teléfono — InputType.PHONE.
45693
+ * `val-profile-content` — CONTENIDO del perfil (avatar + handle + nombre + teléfono
45694
+ * en un form con un botón). Presentacional + lógica de datos, SIN cromática de
45695
+ * página: NO toca `NavigationService` ni el header del shell.
45704
45696
  *
45705
- * Submit handler dispatcha:
45706
- * 1. Si handle cambió updateHandle PRIMERO (uniqueness).
45707
- * 2. Si name o phone cambió → updateProfile con ambos.
45697
+ * Lo reutilizan tanto la vista ruteada (`val-profile-view`, que sí setea el header)
45698
+ * como el modal (`val-profile-modal`). Así el modal no contamina el header del shell.
45708
45699
  *
45709
- * Lectura del perfil: suscribe al doc Firestore /users/{uid} (mirror que mantiene
45710
- * el backend). Reactivo cross-tab + auto-refresca tras save. Fallback REST
45711
- * (auth.getProfile) si el doc no existe o el listener falla.
45712
- *
45713
- * NO renderiza ion-content — vive dentro de val-page-wrapper.
45714
- *
45715
- * Auto-registra sus defaults i18n (es/en) en el constructor si el consumer no
45716
- * proveyó el namespace configurado (default `Settings.Profile`).
45700
+ * Toda la lógica (lectura reactiva del mirror Firestore /users/{uid} + fallback REST,
45701
+ * submit con diff handle→profile, avatar upload, estados, i18n) vive aquí.
45717
45702
  */
45718
- class ProfileViewComponent {
45719
- /** Namespace i18n resuelto (capturado una vez para llamadas no-reactivas). */
45703
+ class ProfileContentComponent {
45704
+ /** Namespace i18n resuelto (público para que la vista ruteada arme el header). */
45720
45705
  get ns() {
45721
45706
  return this.resolvedConfig().i18nNamespace;
45722
45707
  }
45723
45708
  constructor() {
45724
- this.nav = inject(NavigationService);
45725
45709
  this.auth = inject(AuthService);
45726
45710
  this.i18n = inject(I18nService);
45727
45711
  this.toast = inject(ToastService);
@@ -45729,7 +45713,6 @@ class ProfileViewComponent {
45729
45713
  this.firebase = inject(FirebaseService);
45730
45714
  this.collections = inject(FirestoreCollectionFactory);
45731
45715
  this.route = inject(ActivatedRoute, { optional: true });
45732
- /** Config mergeada con defaults. @Input gana sobre route data. */
45733
45716
  this.resolvedConfig = computed(() => {
45734
45717
  const fromRoute = (this.route?.snapshot.data['profileConfig'] ?? {});
45735
45718
  const merged = { ...fromRoute, ...(this.config ?? {}) };
@@ -45739,15 +45722,11 @@ class ProfileViewComponent {
45739
45722
  showPhone: merged.showPhone ?? true,
45740
45723
  showShareCta: merged.showShareCta ?? true,
45741
45724
  profileBaseUrl: merged.profileBaseUrl ?? '',
45742
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$k,
45725
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$l,
45743
45726
  onSaved: merged.onSaved,
45744
45727
  onAvatarUploaded: merged.onAvatarUploaded,
45745
45728
  };
45746
45729
  });
45747
- /**
45748
- * Colección global /users/{uid} (skipAppPrefix: true — el doc vive fuera de
45749
- * apps/{appId}/... porque es cross-app, ver firestore.rules).
45750
- */
45751
45730
  this.usersCol = this.collections.create('users', {
45752
45731
  skipAppPrefix: true,
45753
45732
  });
@@ -45824,17 +45803,9 @@ class ProfileViewComponent {
45824
45803
  };
45825
45804
  });
45826
45805
  this.formMeta = this.buildFormMeta();
45827
- // Auto-registro i18n — respeta override del consumer. El namespace puede
45828
- // venir de @Input.config o del route data; lo resolvemos acá.
45829
45806
  const ns = this.ns;
45830
45807
  this.i18n.registerDefaults(ns, PROFILE_VIEW_I18N);
45831
- this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
45832
45808
  connectPageRefresh(() => this.loadFromApi());
45833
- // Cargar perfil inmediatamente si el usuario ya está autenticado.
45834
- // Sin esto, si auth.user() ya estaba populado al montar el componente
45835
- // (sesión restaurada), el effect no dispara y la página queda vacía
45836
- // hasta que el usuario hace pull-to-refresh (Regla: no esperar a que
45837
- // una signal cambie si ya tiene valor inicial).
45838
45809
  void this.firebase.whenFirebaseAuthReady().then(() => {
45839
45810
  const uid = this._userId();
45840
45811
  if (uid) {
@@ -45880,7 +45851,6 @@ class ProfileViewComponent {
45880
45851
  sub?.unsubscribe();
45881
45852
  });
45882
45853
  }, { allowSignalWrites: true });
45883
- // saving → mutar state (val-form.ngDoCheck lo detecta).
45884
45854
  effect(() => {
45885
45855
  const isSaving = this.saving();
45886
45856
  const state = isSaving ? ComponentStates.WORKING : ComponentStates.ENABLED;
@@ -45888,7 +45858,6 @@ class ProfileViewComponent {
45888
45858
  this.formMeta.actions.state = state;
45889
45859
  this.formMeta.actions.text = isSaving ? this.t('saving') : this.t('save');
45890
45860
  });
45891
- // profile loaded + form mounted → patchValue al FormGroup.
45892
45861
  effect(() => {
45893
45862
  const p = this.profile();
45894
45863
  const form = this.formCmp()?.Form;
@@ -45900,13 +45869,11 @@ class ProfileViewComponent {
45900
45869
  phone: p.phone || '',
45901
45870
  }, { emitEvent: false });
45902
45871
  });
45903
- // i18n.lang change → mutar labels (rare, no afecta typing).
45904
45872
  effect(() => {
45905
45873
  this.i18n.lang();
45906
45874
  this.applyI18nLabels();
45907
45875
  });
45908
45876
  }
45909
- /** Construcción inicial — UNA vez. Gatea handle/phone según config. */
45910
45877
  buildFormMeta() {
45911
45878
  const cfg = this.resolvedConfig();
45912
45879
  const handleField = {
@@ -45959,7 +45926,6 @@ class ProfileViewComponent {
45959
45926
  value: '',
45960
45927
  state: ComponentStates.ENABLED,
45961
45928
  };
45962
- // Filtrado por config: el name siempre va; handle/phone son opt-out.
45963
45929
  const fields = [];
45964
45930
  if (cfg.showHandle)
45965
45931
  fields.push(handleField);
@@ -45990,7 +45956,6 @@ class ProfileViewComponent {
45990
45956
  actions: submitButton,
45991
45957
  };
45992
45958
  }
45993
- /** Re-aplica labels/hints/placeholders en cambio de idioma. */
45994
45959
  applyI18nLabels() {
45995
45960
  const section = this.formMeta.sections[0];
45996
45961
  for (const field of section.fields) {
@@ -46021,10 +45986,6 @@ class ProfileViewComponent {
46021
45986
  const button = this.formMeta.actions;
46022
45987
  button.text = this.saving() ? this.t('saving') : this.t('save');
46023
45988
  }
46024
- /**
46025
- * Fallback REST. Capa 4 (UI declarativa): NO toasteamos acá — el
46026
- * val-empty-state ES la UI del error.
46027
- */
46028
45989
  async loadFromApi() {
46029
45990
  this.loading.set(true);
46030
45991
  try {
@@ -46149,7 +46110,6 @@ class ProfileViewComponent {
46149
46110
  position: 'top',
46150
46111
  });
46151
46112
  }
46152
- /** Lee del namespace configurado. */
46153
46113
  t(key) {
46154
46114
  return this.i18n.t(key, this.ns);
46155
46115
  }
@@ -46163,8 +46123,8 @@ class ProfileViewComponent {
46163
46123
  const last = parts.length > 1 ? (parts[parts.length - 1]?.[0] ?? '') : '';
46164
46124
  return (first + last).toUpperCase() || '?';
46165
46125
  }
46166
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProfileViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
46167
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ProfileViewComponent, isStandalone: true, selector: "val-profile-view", inputs: { config: "config" }, viewQueries: [{ propertyName: "formCmp", first: true, predicate: ["form"], descendants: true, isSignal: true }], ngImport: i0, template: `
46126
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProfileContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
46127
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ProfileContentComponent, isStandalone: true, selector: "val-profile-content", inputs: { config: "config" }, viewQueries: [{ propertyName: "formCmp", first: true, predicate: ["form"], descendants: true, isSignal: true }], ngImport: i0, template: `
46168
46128
  <div class="page">
46169
46129
  <header class="page-header">
46170
46130
  <val-display [props]="{ size: 'small', color: 'dark', content: t('pageTitle') }" />
@@ -46223,17 +46183,14 @@ class ProfileViewComponent {
46223
46183
  }
46224
46184
 
46225
46185
  <val-form #form [props]="formMeta" (onSubmit)="onSubmit($event)" />
46226
-
46227
- <!-- Card de compartir + modal QR removidos temporalmente (se retoman luego).
46228
- Wiring intacto: shareCta(), shareModal(), shareOpen, showShareCta. -->
46229
46186
  }
46230
46187
  }
46231
46188
  </div>
46232
- `, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.page-header h1{font-size:22px;font-weight:700;margin:0 0 4px}.hint{font-size:13px;opacity:.7;margin:0 0 12px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.avatar-section{display:flex;align-items:center;gap:16px}.avatar-meta{display:flex;flex-direction:column;gap:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: AvatarUploadComponent, selector: "val-avatar-upload", inputs: ["props", "customPath", "customThumbPath", "skipBackendSync"], outputs: ["uploaded", "error", "uploadStart"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", inputs: ["props"] }] }); }
46189
+ `, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.avatar-section{display:flex;align-items:center;gap:16px}.avatar-meta{display:flex;flex-direction:column;gap:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: AvatarUploadComponent, selector: "val-avatar-upload", inputs: ["props", "customPath", "customThumbPath", "skipBackendSync"], outputs: ["uploaded", "error", "uploadStart"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", inputs: ["props"] }] }); }
46233
46190
  }
46234
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProfileViewComponent, decorators: [{
46191
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProfileContentComponent, decorators: [{
46235
46192
  type: Component,
46236
- args: [{ selector: 'val-profile-view', standalone: true, imports: [
46193
+ args: [{ selector: 'val-profile-content', standalone: true, imports: [
46237
46194
  CommonModule,
46238
46195
  ReactiveFormsModule,
46239
46196
  AvatarUploadComponent,
@@ -46304,13 +46261,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
46304
46261
  }
46305
46262
 
46306
46263
  <val-form #form [props]="formMeta" (onSubmit)="onSubmit($event)" />
46307
-
46308
- <!-- Card de compartir + modal QR removidos temporalmente (se retoman luego).
46309
- Wiring intacto: shareCta(), shareModal(), shareOpen, showShareCta. -->
46310
46264
  }
46311
46265
  }
46312
46266
  </div>
46313
- `, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.page-header h1{font-size:22px;font-weight:700;margin:0 0 4px}.hint{font-size:13px;opacity:.7;margin:0 0 12px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.avatar-section{display:flex;align-items:center;gap:16px}.avatar-meta{display:flex;flex-direction:column;gap:2px}\n"] }]
46267
+ `, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.avatar-section{display:flex;align-items:center;gap:16px}.avatar-meta{display:flex;flex-direction:column;gap:2px}\n"] }]
46268
+ }], ctorParameters: () => [], propDecorators: { config: [{
46269
+ type: Input
46270
+ }] } });
46271
+
46272
+ const DEFAULT_NAMESPACE$k = 'Settings.Profile';
46273
+ /**
46274
+ * `val-profile-view` — vista Perfil ruteada (organism). Wrapper delgado:
46275
+ * configura el header del shell (`NavigationService.setBackHeader`) y delega TODO
46276
+ * el contenido/lógica a `val-profile-content`.
46277
+ *
46278
+ * El contenido vive en `val-profile-content` (sin cromática de página) para poder
46279
+ * reutilizarse también desde `val-profile-modal` sin contaminar el header.
46280
+ *
46281
+ * Consumo: igual que antes (ruta vía `provideValtechProfileRoutes` o embebida con
46282
+ * `[config]`). La config se pasa tal cual al contenido.
46283
+ */
46284
+ class ProfileViewComponent {
46285
+ constructor() {
46286
+ this.nav = inject(NavigationService);
46287
+ this.i18n = inject(I18nService);
46288
+ this.route = inject(ActivatedRoute, { optional: true });
46289
+ this.ns = computed(() => {
46290
+ const fromRoute = (this.route?.snapshot.data['profileConfig'] ?? {});
46291
+ return this.config?.i18nNamespace ?? fromRoute.i18nNamespace ?? DEFAULT_NAMESPACE$k;
46292
+ });
46293
+ const ns = this.ns();
46294
+ // El header usa la key 'pageTitle' del namespace; el contenido registra los
46295
+ // defaults i18n, pero lo aseguramos también acá por si el header resuelve antes.
46296
+ this.i18n.registerDefaults(ns, PROFILE_VIEW_I18N);
46297
+ this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
46298
+ }
46299
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProfileViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
46300
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ProfileViewComponent, isStandalone: true, selector: "val-profile-view", inputs: { config: "config" }, ngImport: i0, template: ` <val-profile-content [config]="config" /> `, isInline: true, dependencies: [{ kind: "component", type: ProfileContentComponent, selector: "val-profile-content", inputs: ["config"] }] }); }
46301
+ }
46302
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProfileViewComponent, decorators: [{
46303
+ type: Component,
46304
+ args: [{
46305
+ selector: 'val-profile-view',
46306
+ standalone: true,
46307
+ imports: [ProfileContentComponent],
46308
+ template: ` <val-profile-content [config]="config" /> `,
46309
+ }]
46314
46310
  }], ctorParameters: () => [], propDecorators: { config: [{
46315
46311
  type: Input
46316
46312
  }] } });
@@ -59442,16 +59438,16 @@ class ProfileModalComponent {
59442
59438
  </ion-toolbar>
59443
59439
  </ion-header>
59444
59440
  <ion-content class="ion-padding">
59445
- <val-profile-view />
59441
+ <val-profile-content />
59446
59442
  </ion-content>
59447
- `, isInline: true, dependencies: [{ kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: ProfileViewComponent, selector: "val-profile-view", inputs: ["config"] }] }); }
59443
+ `, isInline: true, dependencies: [{ kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: ProfileContentComponent, selector: "val-profile-content", inputs: ["config"] }] }); }
59448
59444
  }
59449
59445
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProfileModalComponent, decorators: [{
59450
59446
  type: Component,
59451
59447
  args: [{
59452
59448
  selector: 'val-profile-modal',
59453
59449
  standalone: true,
59454
- imports: [IonButton, IonButtons, IonContent, IonHeader, IonToolbar, ProfileViewComponent],
59450
+ imports: [IonButton, IonButtons, IonContent, IonHeader, IonToolbar, ProfileContentComponent],
59455
59451
  template: `
59456
59452
  <ion-header>
59457
59453
  <ion-toolbar>
@@ -59463,7 +59459,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
59463
59459
  </ion-toolbar>
59464
59460
  </ion-header>
59465
59461
  <ion-content class="ion-padding">
59466
- <val-profile-view />
59462
+ <val-profile-content />
59467
59463
  </ion-content>
59468
59464
  `,
59469
59465
  }]
@@ -62335,7 +62331,6 @@ class HeaderActionsService {
62335
62331
  constructor() {
62336
62332
  this.modal = inject(ModalService);
62337
62333
  this.router = inject(Router);
62338
- this.nav = inject(NavigationService);
62339
62334
  }
62340
62335
  async handle(token, config = {}) {
62341
62336
  const notificationsRoute = config.notificationsRoute ?? '/app/notifications';
@@ -62346,11 +62341,9 @@ class HeaderActionsService {
62346
62341
  await this.router.navigate([notificationsRoute]);
62347
62342
  return true;
62348
62343
  case 'profile':
62344
+ // El modal usa val-profile-content (sin cromática de página) → no toca
62345
+ // el header del shell, así que no hace falta restaurarlo al cerrar.
62349
62346
  await this.modal.open({ component: ProfileModalComponent });
62350
- // val-profile-view setea el header del shell (es una vista de settings
62351
- // ruteada). Al reusarla en el modal contamina el header; lo restauramos
62352
- // al home al cerrar para no dejar el híbrido perfil/home.
62353
- this.nav.resetHeader();
62354
62347
  return true;
62355
62348
  case 'org':
62356
62349
  await this.modal.open({
@@ -73010,20 +73003,23 @@ const CHAT_COMPOSER_I18N = {
73010
73003
  },
73011
73004
  };
73012
73005
  /**
73013
- * val-chat-composer — barra de redacción de chat moderna (signal inputs).
73006
+ * val-chat-composer — barra de redacción estilo Notion/Claude (full custom).
73007
+ *
73008
+ * El editor es un `contenteditable` (NO ion-textarea): crece hasta un max-height,
73009
+ * soporta pegar texto plano e imágenes del portapapeles, Enter envía / Shift+Enter
73010
+ * salta línea. Debajo, una fila de acciones propias: adjuntar (+), mic/enviar.
73011
+ * Los adjuntos (botón / pegar / audio grabado) se acumulan como miniaturas de
73012
+ * PREVIEW y se mandan en UN solo evento `send {text, files}` (caption + adjuntos).
73014
73013
  *
73015
- * Pill con: adjuntar (+), textarea auto-grow, y mic/enviar (mic cuando está vacío,
73016
- * enviar cuando hay texto o adjuntos). Los adjuntos (botón + / pegar imagen) se
73017
- * acumulan como PREVIEW (miniaturas) y se emiten por `(attach)` recién al ENVIAR
73018
- * — no se van solos al chat. Audio grabado se emite directo al parar.
73019
- * No incluye picker de emoji: el teclado del sistema ya lo trae.
73014
+ * Pensado para vivir en un contenedor fijo al fondo del viewport (el consumer
73015
+ * ancla la posición). No incluye emoji picker (el teclado del sistema lo trae).
73020
73016
  */
73021
73017
  class ChatComposerComponent {
73022
73018
  constructor() {
73023
73019
  this.i18n = inject(I18nService);
73024
73020
  this.placeholder = input('');
73025
73021
  this.disabled = input(false);
73026
- this.maxLength = input(2000);
73022
+ this.maxLength = input(4000);
73027
73023
  this.replyingTo = input(null);
73028
73024
  this.showAttach = input(true);
73029
73025
  this.showMic = input(true);
@@ -73031,6 +73027,7 @@ class ChatComposerComponent {
73031
73027
  this.typing = output();
73032
73028
  this.voice = output();
73033
73029
  this.cancelReply = output();
73030
+ this.editableRef = viewChild('editable');
73034
73031
  this.body = signal('');
73035
73032
  this.pending = signal([]);
73036
73033
  this.pendingId = 0;
@@ -73046,9 +73043,9 @@ class ChatComposerComponent {
73046
73043
  });
73047
73044
  this.lastTypingEmit = 0;
73048
73045
  this.canSend = computed(() => {
73049
- const t = this.body().trim();
73050
73046
  if (this.disabled())
73051
73047
  return false;
73048
+ const t = this.body().trim();
73052
73049
  return (t.length > 0 && t.length <= this.maxLength()) || this.pending().length > 0;
73053
73050
  });
73054
73051
  this.placeholderText = computed(() => {
@@ -73057,9 +73054,12 @@ class ChatComposerComponent {
73057
73054
  });
73058
73055
  this.i18n.registerDefaults('ChatComposer', CHAT_COMPOSER_I18N);
73059
73056
  }
73060
- onInput(event) {
73061
- const target = event.target;
73062
- this.body.set(target.value ?? '');
73057
+ /** Lee el texto del contenteditable a la signal `body`. */
73058
+ syncBody() {
73059
+ const el = this.editableRef()?.nativeElement;
73060
+ // innerText conserva los saltos de línea; quita el \n final que el navegador agrega.
73061
+ const text = (el?.innerText ?? '').replace(/\n$/, '');
73062
+ this.body.set(text);
73063
73063
  this.emitTypingDebounced();
73064
73064
  }
73065
73065
  onKeydown(event) {
@@ -73068,11 +73068,30 @@ class ChatComposerComponent {
73068
73068
  this.onSend();
73069
73069
  }
73070
73070
  }
73071
+ /** Pegar: texto plano (sin formato) + imágenes del portapapeles. */
73072
+ onPaste(event) {
73073
+ event.preventDefault();
73074
+ const data = event.clipboardData;
73075
+ if (!data)
73076
+ return;
73077
+ // Imágenes (capturas / copiar imagen).
73078
+ for (const item of Array.from(data.items)) {
73079
+ if (item.type.startsWith('image/')) {
73080
+ const file = item.getAsFile();
73081
+ if (file)
73082
+ this.addPending(file);
73083
+ }
73084
+ }
73085
+ // Texto plano insertado en el caret (evita pegar HTML con estilos).
73086
+ const text = data.getData('text/plain');
73087
+ if (text) {
73088
+ document.execCommand('insertText', false, text);
73089
+ }
73090
+ this.syncBody();
73091
+ }
73071
73092
  onSend() {
73072
73093
  if (!this.canSend())
73073
73094
  return;
73074
- // Un solo evento: texto (caption) + todos los adjuntos staged. El consumer
73075
- // los sube y manda como UN mensaje con body + attachments.
73076
73095
  const files = this.pending().map(p => p.file);
73077
73096
  const text = this.body().trim();
73078
73097
  this.send.emit({ text, files });
@@ -73081,6 +73100,12 @@ class ChatComposerComponent {
73081
73100
  URL.revokeObjectURL(att.url);
73082
73101
  }
73083
73102
  this.pending.set([]);
73103
+ this.clearEditable();
73104
+ }
73105
+ clearEditable() {
73106
+ const el = this.editableRef()?.nativeElement;
73107
+ if (el)
73108
+ el.innerText = '';
73084
73109
  this.body.set('');
73085
73110
  }
73086
73111
  onFile(event) {
@@ -73090,22 +73115,6 @@ class ChatComposerComponent {
73090
73115
  this.addPending(file);
73091
73116
  input.value = '';
73092
73117
  }
73093
- /** Pegar imagen desde el portapapeles (captura de pantalla, copiar imagen). */
73094
- onPaste(event) {
73095
- const items = event.clipboardData?.items;
73096
- if (!items)
73097
- return;
73098
- for (const item of Array.from(items)) {
73099
- if (item.type.startsWith('image/')) {
73100
- const file = item.getAsFile();
73101
- if (file) {
73102
- event.preventDefault();
73103
- this.addPending(file);
73104
- }
73105
- return;
73106
- }
73107
- }
73108
- }
73109
73118
  addPending(file) {
73110
73119
  const kind = file.type.startsWith('image/')
73111
73120
  ? 'image'
@@ -73123,9 +73132,6 @@ class ChatComposerComponent {
73123
73132
  return arr.filter(a => a.id !== id);
73124
73133
  });
73125
73134
  }
73126
- /**
73127
- * Emite typing como máximo 1 vez cada 2s. performance.now es seguro en runtime.
73128
- */
73129
73135
  emitTypingDebounced() {
73130
73136
  const now = performance.now();
73131
73137
  if (now - this.lastTypingEmit > 2000) {
@@ -73134,7 +73140,6 @@ class ChatComposerComponent {
73134
73140
  }
73135
73141
  }
73136
73142
  // ─── Grabación de audio ───────────────────────────────────────────────────
73137
- /** Selecciona un mimeType de audio soportado por el navegador. */
73138
73143
  pickAudioMime() {
73139
73144
  const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4'];
73140
73145
  const MR = window.MediaRecorder;
@@ -73146,7 +73151,7 @@ class ChatComposerComponent {
73146
73151
  if (this.recording())
73147
73152
  return;
73148
73153
  if (!navigator.mediaDevices?.getUserMedia || !('MediaRecorder' in window)) {
73149
- this.voice.emit(); // sin soporte: deja que el consumer decida (fallback)
73154
+ this.voice.emit();
73150
73155
  return;
73151
73156
  }
73152
73157
  try {
@@ -73221,7 +73226,7 @@ class ChatComposerComponent {
73221
73226
  return this.i18n.t(key, 'ChatComposer');
73222
73227
  }
73223
73228
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
73224
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChatComposerComponent, isStandalone: true, selector: "val-chat-composer", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, replyingTo: { classPropertyName: "replyingTo", publicName: "replyingTo", isSignal: true, isRequired: false, transformFunction: null }, showAttach: { classPropertyName: "showAttach", publicName: "showAttach", isSignal: true, isRequired: false, transformFunction: null }, showMic: { classPropertyName: "showMic", publicName: "showMic", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { send: "send", typing: "typing", voice: "voice", cancelReply: "cancelReply" }, ngImport: i0, template: `
73229
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChatComposerComponent, isStandalone: true, selector: "val-chat-composer", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, replyingTo: { classPropertyName: "replyingTo", publicName: "replyingTo", isSignal: true, isRequired: false, transformFunction: null }, showAttach: { classPropertyName: "showAttach", publicName: "showAttach", isSignal: true, isRequired: false, transformFunction: null }, showMic: { classPropertyName: "showMic", publicName: "showMic", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { send: "send", typing: "typing", voice: "voice", cancelReply: "cancelReply" }, viewQueries: [{ propertyName: "editableRef", first: true, predicate: ["editable"], descendants: true, isSignal: true }], ngImport: i0, template: `
73225
73230
  <div class="composer">
73226
73231
  @if (replyingTo()) {
73227
73232
  <div class="reply-bar">
@@ -73275,48 +73280,47 @@ class ChatComposerComponent {
73275
73280
  </button>
73276
73281
  </div>
73277
73282
  } @else {
73278
- <div class="pill">
73279
- @if (showAttach()) {
73280
- <label class="icon-btn" [attr.aria-label]="t('attach')">
73281
- <ion-icon name="add-outline" aria-hidden="true" />
73282
- <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
73283
- </label>
73284
- }
73285
-
73286
- <ion-textarea
73287
- class="field"
73288
- [placeholder]="placeholderText()"
73289
- [disabled]="disabled()"
73290
- [maxlength]="maxLength()"
73291
- [autoGrow]="true"
73292
- [rows]="1"
73293
- [value]="body()"
73294
- [spellcheck]="false"
73295
- autocapitalize="sentences"
73296
- autocorrect="off"
73297
- enterkeyhint="enter"
73298
- (ionInput)="onInput($event)"
73283
+ <div class="shell">
73284
+ <div
73285
+ #editable
73286
+ class="editable"
73287
+ [class.is-empty]="!body()"
73288
+ contenteditable="true"
73289
+ role="textbox"
73290
+ aria-multiline="true"
73291
+ [attr.data-placeholder]="placeholderText()"
73292
+ [attr.aria-label]="placeholderText()"
73293
+ (input)="syncBody()"
73299
73294
  (keydown)="onKeydown($event)"
73300
73295
  (paste)="onPaste($event)"
73301
- ></ion-textarea>
73296
+ ></div>
73302
73297
 
73303
- @if (canSend()) {
73304
- <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73305
- <ion-icon name="arrow-up-outline" aria-hidden="true" />
73306
- </button>
73307
- } @else if (showMic()) {
73308
- <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
73309
- <ion-icon name="mic-outline" aria-hidden="true" />
73310
- </button>
73311
- }
73298
+ <div class="actions">
73299
+ @if (showAttach()) {
73300
+ <label class="icon-btn" [attr.aria-label]="t('attach')">
73301
+ <ion-icon name="add-outline" aria-hidden="true" />
73302
+ <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
73303
+ </label>
73304
+ }
73305
+ <span class="actions-spacer"></span>
73306
+ @if (canSend()) {
73307
+ <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73308
+ <ion-icon name="arrow-up-outline" aria-hidden="true" />
73309
+ </button>
73310
+ } @else if (showMic()) {
73311
+ <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
73312
+ <ion-icon name="mic-outline" aria-hidden="true" />
73313
+ </button>
73314
+ }
73315
+ </div>
73312
73316
  </div>
73313
73317
  }
73314
73318
  </div>
73315
- `, isInline: true, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.pending-row{display:flex;gap:8px;flex-wrap:wrap;padding:2px 4px}.thumb{position:relative;width:64px;height:64px;border-radius:10px;overflow:hidden;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.thumb img{width:100%;height:100%;object-fit:cover}.thumb.file{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;padding:4px;color:var(--ion-color-medium, #92949c);font-size:1.5rem}.thumb-name{font-size:.5625rem;line-height:1.1;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ion-text-color, #000)}.thumb-remove{position:absolute;top:2px;right:2px;width:20px;height:20px;border:none;border-radius:50%;background:#0000008c;color:#fff;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.875rem}.pill{display:flex;align-items:flex-end;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.field{flex:1;--background: transparent;--padding-start: 6px;--padding-end: 6px;--padding-top: 8px;--padding-bottom: 8px;margin:0;max-height:120px;font-size:.9375rem}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.375rem}.icon-btn:hover,.icon-btn.active{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.25rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}.audio-chip{display:flex;align-items:center;gap:6px;width:100%;padding:4px 6px;border-radius:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.audio-chip audio{flex:1;height:36px;min-width:0}.thumb-remove.inline{position:static;flex-shrink:0;background:var(--ion-color-medium, #92949c)}.icon-btn.danger{color:var(--ion-color-danger, #eb445a)}.rec-bar{display:flex;align-items:center;gap:10px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.rec-dot{width:10px;height:10px;border-radius:50%;background:var(--ion-color-danger, #eb445a);animation:rec-pulse 1.2s ease-in-out infinite}.rec-time{font-variant-numeric:tabular-nums;font-size:.9375rem;color:var(--ion-text-color, #000)}.rec-spacer{flex:1}@keyframes rec-pulse{0%,to{opacity:1}50%{opacity:.3}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
73319
+ `, isInline: true, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.pending-row{display:flex;gap:8px;flex-wrap:wrap;padding:2px 4px}.thumb{position:relative;width:64px;height:64px;border-radius:10px;overflow:hidden;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.thumb img{width:100%;height:100%;object-fit:cover}.thumb.file{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;padding:4px;color:var(--ion-color-medium, #92949c);font-size:1.5rem}.thumb-name{font-size:.5625rem;line-height:1.1;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ion-text-color, #000)}.thumb-remove{position:absolute;top:2px;right:2px;width:20px;height:20px;border:none;border-radius:50%;background:#0000008c;color:#fff;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.875rem}.shell{display:flex;flex-direction:column;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:20px;padding:8px 12px}.editable{min-height:24px;max-height:160px;overflow-y:auto;outline:none;font-size:.9375rem;line-height:1.4;color:var(--ion-text-color, #000);white-space:pre-wrap;word-break:break-word;-webkit-user-select:text;user-select:text}.editable.is-empty:before{content:attr(data-placeholder);color:var(--ion-color-medium, #92949c);pointer-events:none}.actions{display:flex;align-items:center;gap:4px}.actions-spacer{flex:1}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.25rem}.icon-btn:hover{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.125rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}.icon-btn.danger{color:var(--ion-color-danger, #eb445a)}.audio-chip{display:flex;align-items:center;gap:6px;width:100%;padding:4px 6px;border-radius:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.audio-chip audio{flex:1;height:36px;min-width:0}.thumb-remove.inline{position:static;flex-shrink:0;background:var(--ion-color-medium, #92949c)}.rec-bar{display:flex;align-items:center;gap:10px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:20px;padding:4px 8px}.rec-dot{width:10px;height:10px;border-radius:50%;background:var(--ion-color-danger, #eb445a);animation:rec-pulse 1.2s ease-in-out infinite}.rec-time{font-variant-numeric:tabular-nums;font-size:.9375rem;color:var(--ion-text-color, #000)}.rec-spacer{flex:1}@keyframes rec-pulse{0%,to{opacity:1}50%{opacity:.3}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
73316
73320
  }
73317
73321
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, decorators: [{
73318
73322
  type: Component,
73319
- args: [{ selector: 'val-chat-composer', standalone: true, imports: [CommonModule, IonTextarea, IonIcon], template: `
73323
+ args: [{ selector: 'val-chat-composer', standalone: true, imports: [CommonModule, IonIcon], template: `
73320
73324
  <div class="composer">
73321
73325
  @if (replyingTo()) {
73322
73326
  <div class="reply-bar">
@@ -73370,44 +73374,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
73370
73374
  </button>
73371
73375
  </div>
73372
73376
  } @else {
73373
- <div class="pill">
73374
- @if (showAttach()) {
73375
- <label class="icon-btn" [attr.aria-label]="t('attach')">
73376
- <ion-icon name="add-outline" aria-hidden="true" />
73377
- <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
73378
- </label>
73379
- }
73380
-
73381
- <ion-textarea
73382
- class="field"
73383
- [placeholder]="placeholderText()"
73384
- [disabled]="disabled()"
73385
- [maxlength]="maxLength()"
73386
- [autoGrow]="true"
73387
- [rows]="1"
73388
- [value]="body()"
73389
- [spellcheck]="false"
73390
- autocapitalize="sentences"
73391
- autocorrect="off"
73392
- enterkeyhint="enter"
73393
- (ionInput)="onInput($event)"
73377
+ <div class="shell">
73378
+ <div
73379
+ #editable
73380
+ class="editable"
73381
+ [class.is-empty]="!body()"
73382
+ contenteditable="true"
73383
+ role="textbox"
73384
+ aria-multiline="true"
73385
+ [attr.data-placeholder]="placeholderText()"
73386
+ [attr.aria-label]="placeholderText()"
73387
+ (input)="syncBody()"
73394
73388
  (keydown)="onKeydown($event)"
73395
73389
  (paste)="onPaste($event)"
73396
- ></ion-textarea>
73390
+ ></div>
73397
73391
 
73398
- @if (canSend()) {
73399
- <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73400
- <ion-icon name="arrow-up-outline" aria-hidden="true" />
73401
- </button>
73402
- } @else if (showMic()) {
73403
- <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
73404
- <ion-icon name="mic-outline" aria-hidden="true" />
73405
- </button>
73406
- }
73392
+ <div class="actions">
73393
+ @if (showAttach()) {
73394
+ <label class="icon-btn" [attr.aria-label]="t('attach')">
73395
+ <ion-icon name="add-outline" aria-hidden="true" />
73396
+ <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
73397
+ </label>
73398
+ }
73399
+ <span class="actions-spacer"></span>
73400
+ @if (canSend()) {
73401
+ <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73402
+ <ion-icon name="arrow-up-outline" aria-hidden="true" />
73403
+ </button>
73404
+ } @else if (showMic()) {
73405
+ <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
73406
+ <ion-icon name="mic-outline" aria-hidden="true" />
73407
+ </button>
73408
+ }
73409
+ </div>
73407
73410
  </div>
73408
73411
  }
73409
73412
  </div>
73410
- `, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.pending-row{display:flex;gap:8px;flex-wrap:wrap;padding:2px 4px}.thumb{position:relative;width:64px;height:64px;border-radius:10px;overflow:hidden;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.thumb img{width:100%;height:100%;object-fit:cover}.thumb.file{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;padding:4px;color:var(--ion-color-medium, #92949c);font-size:1.5rem}.thumb-name{font-size:.5625rem;line-height:1.1;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ion-text-color, #000)}.thumb-remove{position:absolute;top:2px;right:2px;width:20px;height:20px;border:none;border-radius:50%;background:#0000008c;color:#fff;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.875rem}.pill{display:flex;align-items:flex-end;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.field{flex:1;--background: transparent;--padding-start: 6px;--padding-end: 6px;--padding-top: 8px;--padding-bottom: 8px;margin:0;max-height:120px;font-size:.9375rem}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.375rem}.icon-btn:hover,.icon-btn.active{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.25rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}.audio-chip{display:flex;align-items:center;gap:6px;width:100%;padding:4px 6px;border-radius:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.audio-chip audio{flex:1;height:36px;min-width:0}.thumb-remove.inline{position:static;flex-shrink:0;background:var(--ion-color-medium, #92949c)}.icon-btn.danger{color:var(--ion-color-danger, #eb445a)}.rec-bar{display:flex;align-items:center;gap:10px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.rec-dot{width:10px;height:10px;border-radius:50%;background:var(--ion-color-danger, #eb445a);animation:rec-pulse 1.2s ease-in-out infinite}.rec-time{font-variant-numeric:tabular-nums;font-size:.9375rem;color:var(--ion-text-color, #000)}.rec-spacer{flex:1}@keyframes rec-pulse{0%,to{opacity:1}50%{opacity:.3}}\n"] }]
73413
+ `, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.pending-row{display:flex;gap:8px;flex-wrap:wrap;padding:2px 4px}.thumb{position:relative;width:64px;height:64px;border-radius:10px;overflow:hidden;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.thumb img{width:100%;height:100%;object-fit:cover}.thumb.file{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;padding:4px;color:var(--ion-color-medium, #92949c);font-size:1.5rem}.thumb-name{font-size:.5625rem;line-height:1.1;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ion-text-color, #000)}.thumb-remove{position:absolute;top:2px;right:2px;width:20px;height:20px;border:none;border-radius:50%;background:#0000008c;color:#fff;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.875rem}.shell{display:flex;flex-direction:column;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:20px;padding:8px 12px}.editable{min-height:24px;max-height:160px;overflow-y:auto;outline:none;font-size:.9375rem;line-height:1.4;color:var(--ion-text-color, #000);white-space:pre-wrap;word-break:break-word;-webkit-user-select:text;user-select:text}.editable.is-empty:before{content:attr(data-placeholder);color:var(--ion-color-medium, #92949c);pointer-events:none}.actions{display:flex;align-items:center;gap:4px}.actions-spacer{flex:1}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.25rem}.icon-btn:hover{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.125rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}.icon-btn.danger{color:var(--ion-color-danger, #eb445a)}.audio-chip{display:flex;align-items:center;gap:6px;width:100%;padding:4px 6px;border-radius:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.audio-chip audio{flex:1;height:36px;min-width:0}.thumb-remove.inline{position:static;flex-shrink:0;background:var(--ion-color-medium, #92949c)}.rec-bar{display:flex;align-items:center;gap:10px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:20px;padding:4px 8px}.rec-dot{width:10px;height:10px;border-radius:50%;background:var(--ion-color-danger, #eb445a);animation:rec-pulse 1.2s ease-in-out infinite}.rec-time{font-variant-numeric:tabular-nums;font-size:.9375rem;color:var(--ion-text-color, #000)}.rec-spacer{flex:1}@keyframes rec-pulse{0%,to{opacity:1}50%{opacity:.3}}\n"] }]
73411
73414
  }], ctorParameters: () => [] });
73412
73415
 
73413
73416
  /**
@@ -74066,5 +74069,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
74066
74069
  * Generated bundle index. Do not edit.
74067
74070
  */
74068
74071
 
74069
- export { 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, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, ApiKeyCreateModalComponent, ApiKeyService, ApiKeysModalComponent, ApiKeysViewComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, ChatComposerComponent, ChatInputComponent, ChatWindowComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentLoaderComponent, ContentReactionComponent, 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_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, 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, DataTableComponent, 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, EditOrgModalComponent, EmptyStateComponent, EntityCardComponent, EntityFeedService, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FieldListComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeSelectorComponent, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormFieldComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, 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, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGGED_IN_HINT_COOKIE, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LanguageSelectorV2Component, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadMoreComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MemberImportModalComponent, MenuComponent, MessageBubbleComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OptionCardsComponent, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, POST_UPDATE_TS_KEY, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PdfService, PermissionCatalogService, PermissionSelectorComponent, PermissionsModalComponent, PermissionsViewComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, ProcessLinksPipe, ProfileModalComponent, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QrScannerComponent, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, ReactionBarComponent, ReactionsService, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestFormComponent, RequestModalComponent, RequestService, RightsFooterComponent, 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, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SplashComponent, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, 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, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_CHAT_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, 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, VERSION, ValtechErrorService, VerifyViewComponent, WhatsappFabComponent, WhatsappService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, button, canSubmitRequestType, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createPageState, createPermissionLabeler, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, formatClockTime, formatDateSeparator, formatRelativeTime, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, groupPermissionsByScope, guestGuard, hasEmulators, iconButton, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isIonicColor, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAboutRoutes, provideValtechAccountRoutes, provideValtechAds, provideValtechApiKeysRoutes, provideValtechAppConfig, provideValtechAppVersion, provideValtechAppVersionHttp, provideValtechAuth, provideValtechAuthInterceptor, provideValtechChat, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechReactions, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, provideValtechWhatsapp, query, rbacGuard, renderPatternSvgInner, replaceSpecialChars, requestSubmitMode, resolveColor, resolveInputDefaultValue, resolveIonicColor, resolveWebBaseUrl, roleGuard, selectableRequestTypes, storagePaths, superAdminGuard, toArticle };
74072
+ export { 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, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, ApiKeyCreateModalComponent, ApiKeyService, ApiKeysModalComponent, ApiKeysViewComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, ChatComposerComponent, ChatInputComponent, ChatWindowComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentLoaderComponent, ContentReactionComponent, 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_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, 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, DataTableComponent, 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, EditOrgModalComponent, EmptyStateComponent, EntityCardComponent, EntityFeedService, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FieldListComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeSelectorComponent, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormFieldComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, 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, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGGED_IN_HINT_COOKIE, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LanguageSelectorV2Component, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadMoreComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MemberImportModalComponent, MenuComponent, MessageBubbleComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OptionCardsComponent, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, POST_UPDATE_TS_KEY, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PdfService, PermissionCatalogService, PermissionSelectorComponent, PermissionsModalComponent, PermissionsViewComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, ProcessLinksPipe, ProfileContentComponent, ProfileModalComponent, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QrScannerComponent, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, ReactionBarComponent, ReactionsService, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestFormComponent, RequestModalComponent, RequestService, RightsFooterComponent, 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, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SplashComponent, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, 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, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_CHAT_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, 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, VERSION, ValtechErrorService, VerifyViewComponent, WhatsappFabComponent, WhatsappService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, button, canSubmitRequestType, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createPageState, createPermissionLabeler, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, formatClockTime, formatDateSeparator, formatRelativeTime, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, groupPermissionsByScope, guestGuard, hasEmulators, iconButton, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isIonicColor, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAboutRoutes, provideValtechAccountRoutes, provideValtechAds, provideValtechApiKeysRoutes, provideValtechAppConfig, provideValtechAppVersion, provideValtechAppVersionHttp, provideValtechAuth, provideValtechAuthInterceptor, provideValtechChat, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechReactions, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, provideValtechWhatsapp, query, rbacGuard, renderPatternSvgInner, replaceSpecialChars, requestSubmitMode, resolveColor, resolveInputDefaultValue, resolveIonicColor, resolveWebBaseUrl, roleGuard, selectableRequestTypes, storagePaths, superAdminGuard, toArticle };
74070
74073
  //# sourceMappingURL=valtech-components.mjs.map