valtech-components 4.0.231 → 4.0.234

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.231';
59
+ const VERSION = '4.0.234';
60
60
 
61
61
  // Control de estado de refresco (singleton a nivel de módulo)
62
62
  let isRefreshing = false;
@@ -72209,10 +72209,21 @@ class ConversationService {
72209
72209
  return this.http.delete(`${this.baseUrl}/conversations/${convId}/participants/${userId}`);
72210
72210
  }
72211
72211
  /**
72212
- * Envia un mensaje a una conversacion.
72212
+ * Envia un mensaje a una conversacion, opcionalmente con adjuntos.
72213
+ * El backend espera `attachments: { url, type, name, size }[]` (key `type` = MIME),
72214
+ * mientras que el front usa `mimeType` — se mapea aqui.
72213
72215
  */
72214
- sendMessage(convId, body) {
72215
- return this.http.post(`${this.baseUrl}/conversations/${convId}/messages`, { body });
72216
+ sendMessage(convId, body, attachments) {
72217
+ const payload = { body };
72218
+ if (attachments?.length) {
72219
+ payload['attachments'] = attachments.map(a => ({
72220
+ url: a.url,
72221
+ type: a.mimeType,
72222
+ name: a.name,
72223
+ size: a.size ?? 0,
72224
+ }));
72225
+ }
72226
+ return this.http.post(`${this.baseUrl}/conversations/${convId}/messages`, payload);
72216
72227
  }
72217
72228
  /**
72218
72229
  * Elimina (marca como eliminado) un mensaje.
@@ -72439,7 +72450,8 @@ class MessageBubbleComponent {
72439
72450
  return (parts[0][0] + (parts[1]?.[0] ?? '')).toUpperCase();
72440
72451
  });
72441
72452
  this.imageAttachments = computed(() => (this.msg().attachments ?? []).filter(a => a.mimeType?.startsWith('image/')));
72442
- this.fileAttachments = computed(() => (this.msg().attachments ?? []).filter(a => !a.mimeType?.startsWith('image/')));
72453
+ this.audioAttachments = computed(() => (this.msg().attachments ?? []).filter(a => a.mimeType?.startsWith('audio/')));
72454
+ this.fileAttachments = computed(() => (this.msg().attachments ?? []).filter(a => !a.mimeType?.startsWith('image/') && !a.mimeType?.startsWith('audio/')));
72443
72455
  this.statusIcon = computed(() => {
72444
72456
  switch (this.msg().status) {
72445
72457
  case 'sending':
@@ -72510,6 +72522,13 @@ class MessageBubbleComponent {
72510
72522
  }
72511
72523
  </div>
72512
72524
  }
72525
+ @if (audioAttachments().length > 0) {
72526
+ <div class="audios">
72527
+ @for (att of audioAttachments(); track att.url) {
72528
+ <audio class="att-audio" controls preload="metadata" [src]="att.url"></audio>
72529
+ }
72530
+ </div>
72531
+ }
72513
72532
  @if (fileAttachments().length > 0) {
72514
72533
  <div class="files">
72515
72534
  @for (att of fileAttachments(); track att.url) {
@@ -72573,7 +72592,7 @@ class MessageBubbleComponent {
72573
72592
  }
72574
72593
  </div>
72575
72594
  </div>
72576
- `, isInline: true, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\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"] }] }); }
72595
+ `, isInline: true, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.audios{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-audio{width:240px;max-width:100%;height:40px}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\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"] }] }); }
72577
72596
  }
72578
72597
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, decorators: [{
72579
72598
  type: Component,
@@ -72618,6 +72637,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
72618
72637
  }
72619
72638
  </div>
72620
72639
  }
72640
+ @if (audioAttachments().length > 0) {
72641
+ <div class="audios">
72642
+ @for (att of audioAttachments(); track att.url) {
72643
+ <audio class="att-audio" controls preload="metadata" [src]="att.url"></audio>
72644
+ }
72645
+ </div>
72646
+ }
72621
72647
  @if (fileAttachments().length > 0) {
72622
72648
  <div class="files">
72623
72649
  @for (att of fileAttachments(); track att.url) {
@@ -72681,7 +72707,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
72681
72707
  }
72682
72708
  </div>
72683
72709
  </div>
72684
- `, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\n"] }]
72710
+ `, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.audios{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-audio{width:240px;max-width:100%;height:40px}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\n"] }]
72685
72711
  }], ctorParameters: () => [] });
72686
72712
 
72687
72713
  addIcons({ sendOutline });
@@ -72834,35 +72860,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
72834
72860
  type: Output
72835
72861
  }] } });
72836
72862
 
72837
- addIcons({ addOutline, closeOutline, happyOutline, micOutline, arrowUpOutline });
72863
+ addIcons({ addOutline, checkmarkOutline, closeOutline, documentOutline, micOutline, arrowUpOutline, trashOutline });
72838
72864
  const CHAT_COMPOSER_I18N = {
72839
72865
  es: {
72840
72866
  placeholder: 'Escribe un mensaje',
72841
72867
  send: 'Enviar',
72842
72868
  attach: 'Adjuntar',
72843
- emoji: 'Emoji',
72844
72869
  voice: 'Mensaje de voz',
72870
+ done: 'Listo',
72845
72871
  replyingTo: 'Respondiendo a',
72846
72872
  cancel: 'Cancelar',
72873
+ remove: 'Quitar',
72847
72874
  },
72848
72875
  en: {
72849
72876
  placeholder: 'Write a message',
72850
72877
  send: 'Send',
72851
72878
  attach: 'Attach',
72852
- emoji: 'Emoji',
72853
72879
  voice: 'Voice message',
72880
+ done: 'Done',
72854
72881
  replyingTo: 'Replying to',
72855
72882
  cancel: 'Cancel',
72883
+ remove: 'Remove',
72856
72884
  },
72857
72885
  };
72858
- const QUICK_EMOJIS = ['👍', '❤️', '😂', '🎉', '🙏', '🔥', '😮', '😢'];
72859
72886
  /**
72860
72887
  * val-chat-composer — barra de redacción de chat moderna (signal inputs).
72861
72888
  *
72862
- * Pill con: adjuntar (+), textarea auto-grow, emoji (fila rápida) y mic/enviar
72863
- * (mic cuando está vacío, enviar cuando hay texto patrón WhatsApp/Telegram).
72864
- * Muestra una barra de contexto "respondiendo a" cuando `replyingTo` está seteado.
72865
- * `typing` se emite con debounce (máx. 1 vez cada 2s) para no spamear writes.
72889
+ * Pill con: adjuntar (+), textarea auto-grow, y mic/enviar (mic cuando está vacío,
72890
+ * enviar cuando hay texto o adjuntos). Los adjuntos (botón + / pegar imagen) se
72891
+ * acumulan como PREVIEW (miniaturas) y se emiten por `(attach)` recién al ENVIAR
72892
+ * no se van solos al chat. Audio grabado se emite directo al parar.
72893
+ * No incluye picker de emoji: el teclado del sistema ya lo trae.
72866
72894
  */
72867
72895
  class ChatComposerComponent {
72868
72896
  constructor() {
@@ -72872,20 +72900,30 @@ class ChatComposerComponent {
72872
72900
  this.maxLength = input(2000);
72873
72901
  this.replyingTo = input(null);
72874
72902
  this.showAttach = input(true);
72875
- this.showEmoji = input(true);
72876
72903
  this.showMic = input(true);
72877
72904
  this.send = output();
72878
72905
  this.typing = output();
72879
- this.attach = output();
72880
72906
  this.voice = output();
72881
72907
  this.cancelReply = output();
72882
72908
  this.body = signal('');
72883
- this.emojiOpen = signal(false);
72884
- this.quickEmojis = QUICK_EMOJIS;
72909
+ this.pending = signal([]);
72910
+ this.pendingId = 0;
72911
+ // Grabación de audio (MediaRecorder).
72912
+ this.recording = signal(false);
72913
+ this.recSeconds = signal(0);
72914
+ this.recChunks = [];
72915
+ this.recTimeLabel = computed(() => {
72916
+ const s = this.recSeconds();
72917
+ const mm = String(Math.floor(s / 60)).padStart(2, '0');
72918
+ const ss = String(s % 60).padStart(2, '0');
72919
+ return `${mm}:${ss}`;
72920
+ });
72885
72921
  this.lastTypingEmit = 0;
72886
72922
  this.canSend = computed(() => {
72887
72923
  const t = this.body().trim();
72888
- return t.length > 0 && t.length <= this.maxLength() && !this.disabled();
72924
+ if (this.disabled())
72925
+ return false;
72926
+ return (t.length > 0 && t.length <= this.maxLength()) || this.pending().length > 0;
72889
72927
  });
72890
72928
  this.placeholderText = computed(() => {
72891
72929
  this.i18n.lang();
@@ -72905,29 +72943,62 @@ class ChatComposerComponent {
72905
72943
  }
72906
72944
  }
72907
72945
  onSend() {
72908
- const trimmed = this.body().trim();
72909
- if (!trimmed || trimmed.length > this.maxLength())
72946
+ if (!this.canSend())
72910
72947
  return;
72911
- this.send.emit(trimmed);
72948
+ // Un solo evento: texto (caption) + todos los adjuntos staged. El consumer
72949
+ // los sube y manda como UN mensaje con body + attachments.
72950
+ const files = this.pending().map(p => p.file);
72951
+ const text = this.body().trim();
72952
+ this.send.emit({ text, files });
72953
+ for (const att of this.pending()) {
72954
+ if (att.url)
72955
+ URL.revokeObjectURL(att.url);
72956
+ }
72957
+ this.pending.set([]);
72912
72958
  this.body.set('');
72913
- this.emojiOpen.set(false);
72914
- }
72915
- toggleEmoji() {
72916
- this.emojiOpen.update(v => !v);
72917
- }
72918
- insertEmoji(emoji) {
72919
- this.body.update(v => v + emoji);
72920
72959
  }
72921
72960
  onFile(event) {
72922
72961
  const input = event.target;
72923
72962
  const file = input.files?.[0];
72924
72963
  if (file)
72925
- this.attach.emit(file);
72964
+ this.addPending(file);
72926
72965
  input.value = '';
72927
72966
  }
72967
+ /** Pegar imagen desde el portapapeles (captura de pantalla, copiar imagen). */
72968
+ onPaste(event) {
72969
+ const items = event.clipboardData?.items;
72970
+ if (!items)
72971
+ return;
72972
+ for (const item of Array.from(items)) {
72973
+ if (item.type.startsWith('image/')) {
72974
+ const file = item.getAsFile();
72975
+ if (file) {
72976
+ event.preventDefault();
72977
+ this.addPending(file);
72978
+ }
72979
+ return;
72980
+ }
72981
+ }
72982
+ }
72983
+ addPending(file) {
72984
+ const kind = file.type.startsWith('image/')
72985
+ ? 'image'
72986
+ : file.type.startsWith('audio/')
72987
+ ? 'audio'
72988
+ : 'file';
72989
+ const url = kind === 'file' ? undefined : URL.createObjectURL(file);
72990
+ this.pending.update(arr => [...arr, { id: this.pendingId++, file, url, kind }]);
72991
+ }
72992
+ removePending(id) {
72993
+ this.pending.update(arr => {
72994
+ const found = arr.find(a => a.id === id);
72995
+ if (found?.url)
72996
+ URL.revokeObjectURL(found.url);
72997
+ return arr.filter(a => a.id !== id);
72998
+ });
72999
+ }
72928
73000
  /**
72929
- * Emite typing como máximo 1 vez cada 2s. No usa Date.now() directo en plantillas
72930
- * de workflow; aquí estamos en runtime de navegador, performance.now es seguro.
73001
+ * Emite typing como máximo 1 vez cada 2s. performance.now es seguro en runtime.
72931
73002
  */
72932
73003
  emitTypingDebounced() {
72933
73004
  const now = performance.now();
@@ -72936,11 +73007,95 @@ class ChatComposerComponent {
72936
73007
  this.typing.emit();
72937
73008
  }
72938
73009
  }
73010
+ // ─── Grabación de audio ───────────────────────────────────────────────────
73011
+ /** Selecciona un mimeType de audio soportado por el navegador. */
73012
+ pickAudioMime() {
73013
+ const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4'];
73014
+ const MR = window.MediaRecorder;
73015
+ if (!MR?.isTypeSupported)
73016
+ return '';
73017
+ return candidates.find(c => MR.isTypeSupported(c)) ?? '';
73018
+ }
73019
+ async startRecording() {
73020
+ if (this.recording())
73021
+ return;
73022
+ if (!navigator.mediaDevices?.getUserMedia || !('MediaRecorder' in window)) {
73023
+ this.voice.emit(); // sin soporte: deja que el consumer decida (fallback)
73024
+ return;
73025
+ }
73026
+ try {
73027
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
73028
+ this.recStream = stream;
73029
+ const mimeType = this.pickAudioMime();
73030
+ this.recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);
73031
+ this.recChunks = [];
73032
+ this.recorder.ondataavailable = e => {
73033
+ if (e.data.size > 0)
73034
+ this.recChunks.push(e.data);
73035
+ };
73036
+ this.recorder.start();
73037
+ this.recording.set(true);
73038
+ this.recSeconds.set(0);
73039
+ this.recTimer = setInterval(() => this.recSeconds.update(s => s + 1), 1000);
73040
+ }
73041
+ catch {
73042
+ this.cleanupRecording();
73043
+ }
73044
+ }
73045
+ /** Para la grabación y la stagea como adjunto pendiente (con preview de audio). */
73046
+ finishRecording() {
73047
+ const rec = this.recorder;
73048
+ if (!rec) {
73049
+ this.cleanupRecording();
73050
+ return;
73051
+ }
73052
+ rec.onstop = () => {
73053
+ const type = rec.mimeType || 'audio/webm';
73054
+ const blob = new Blob(this.recChunks, { type });
73055
+ const ext = type.includes('mp4') ? 'm4a' : 'webm';
73056
+ const file = new File([blob], `audio-${this.recSeconds()}s.${ext}`, { type });
73057
+ if (blob.size > 0)
73058
+ this.addPending(file);
73059
+ this.cleanupRecording();
73060
+ };
73061
+ rec.stop();
73062
+ }
73063
+ cancelRecording() {
73064
+ const rec = this.recorder;
73065
+ if (rec)
73066
+ rec.onstop = () => this.cleanupRecording();
73067
+ try {
73068
+ rec?.stop();
73069
+ }
73070
+ catch {
73071
+ this.cleanupRecording();
73072
+ }
73073
+ if (!rec)
73074
+ this.cleanupRecording();
73075
+ }
73076
+ cleanupRecording() {
73077
+ if (this.recTimer)
73078
+ clearInterval(this.recTimer);
73079
+ this.recTimer = undefined;
73080
+ this.recStream?.getTracks().forEach(t => t.stop());
73081
+ this.recStream = undefined;
73082
+ this.recorder = undefined;
73083
+ this.recChunks = [];
73084
+ this.recording.set(false);
73085
+ this.recSeconds.set(0);
73086
+ }
73087
+ ngOnDestroy() {
73088
+ this.cleanupRecording();
73089
+ for (const att of this.pending()) {
73090
+ if (att.url)
73091
+ URL.revokeObjectURL(att.url);
73092
+ }
73093
+ }
72939
73094
  t(key) {
72940
73095
  return this.i18n.t(key, 'ChatComposer');
72941
73096
  }
72942
73097
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
72943
- 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 }, showEmoji: { classPropertyName: "showEmoji", publicName: "showEmoji", isSignal: true, isRequired: false, transformFunction: null }, showMic: { classPropertyName: "showMic", publicName: "showMic", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { send: "send", typing: "typing", attach: "attach", voice: "voice", cancelReply: "cancelReply" }, ngImport: i0, template: `
73098
+ 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: `
72944
73099
  <div class="composer">
72945
73100
  @if (replyingTo()) {
72946
73101
  <div class="reply-bar">
@@ -72954,52 +73109,84 @@ class ChatComposerComponent {
72954
73109
  </div>
72955
73110
  }
72956
73111
 
72957
- @if (emojiOpen()) {
72958
- <div class="emoji-row">
72959
- @for (e of quickEmojis; track e) {
72960
- <button class="emoji" (click)="insertEmoji(e)">{{ e }}</button>
73112
+ @if (pending().length > 0) {
73113
+ <div class="pending-row">
73114
+ @for (att of pending(); track att.id) {
73115
+ @if (att.kind === 'audio') {
73116
+ <div class="audio-chip">
73117
+ <audio controls preload="metadata" [src]="att.url"></audio>
73118
+ <button class="thumb-remove inline" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
73119
+ <ion-icon name="close-outline" aria-hidden="true" />
73120
+ </button>
73121
+ </div>
73122
+ } @else {
73123
+ <div class="thumb" [class.file]="att.kind === 'file'">
73124
+ @if (att.kind === 'image') {
73125
+ <img [src]="att.url" [alt]="att.file.name" />
73126
+ } @else {
73127
+ <ion-icon name="document-outline" aria-hidden="true" />
73128
+ <span class="thumb-name">{{ att.file.name }}</span>
73129
+ }
73130
+ <button class="thumb-remove" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
73131
+ <ion-icon name="close-outline" aria-hidden="true" />
73132
+ </button>
73133
+ </div>
73134
+ }
72961
73135
  }
72962
73136
  </div>
72963
73137
  }
72964
73138
 
72965
- <div class="pill">
72966
- @if (showAttach()) {
72967
- <label class="icon-btn" [attr.aria-label]="t('attach')">
72968
- <ion-icon name="add-outline" aria-hidden="true" />
72969
- <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
72970
- </label>
72971
- }
72972
-
72973
- <ion-textarea
72974
- class="field"
72975
- [placeholder]="placeholderText()"
72976
- [disabled]="disabled()"
72977
- [maxlength]="maxLength()"
72978
- [autoGrow]="true"
72979
- [rows]="1"
72980
- [value]="body()"
72981
- (ionInput)="onInput($event)"
72982
- (keydown)="onKeydown($event)"
72983
- ></ion-textarea>
72984
-
72985
- @if (showEmoji()) {
72986
- <button class="icon-btn" [class.active]="emojiOpen()" [attr.aria-label]="t('emoji')" (click)="toggleEmoji()">
72987
- <ion-icon name="happy-outline" aria-hidden="true" />
73139
+ @if (recording()) {
73140
+ <div class="rec-bar">
73141
+ <button class="icon-btn danger" [attr.aria-label]="t('cancel')" (click)="cancelRecording()">
73142
+ <ion-icon name="trash-outline" aria-hidden="true" />
72988
73143
  </button>
72989
- }
72990
-
72991
- @if (canSend()) {
72992
- <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
72993
- <ion-icon name="arrow-up-outline" aria-hidden="true" />
72994
- </button>
72995
- } @else if (showMic()) {
72996
- <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="voice.emit()">
72997
- <ion-icon name="mic-outline" aria-hidden="true" />
73144
+ <span class="rec-dot"></span>
73145
+ <span class="rec-time">{{ recTimeLabel() }}</span>
73146
+ <span class="rec-spacer"></span>
73147
+ <button class="send-btn" [attr.aria-label]="t('done')" (click)="finishRecording()">
73148
+ <ion-icon name="checkmark-outline" aria-hidden="true" />
72998
73149
  </button>
72999
- }
73000
- </div>
73150
+ </div>
73151
+ } @else {
73152
+ <div class="pill">
73153
+ @if (showAttach()) {
73154
+ <label class="icon-btn" [attr.aria-label]="t('attach')">
73155
+ <ion-icon name="add-outline" aria-hidden="true" />
73156
+ <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
73157
+ </label>
73158
+ }
73159
+
73160
+ <ion-textarea
73161
+ class="field"
73162
+ [placeholder]="placeholderText()"
73163
+ [disabled]="disabled()"
73164
+ [maxlength]="maxLength()"
73165
+ [autoGrow]="true"
73166
+ [rows]="1"
73167
+ [value]="body()"
73168
+ [spellcheck]="false"
73169
+ autocapitalize="sentences"
73170
+ autocorrect="off"
73171
+ enterkeyhint="enter"
73172
+ (ionInput)="onInput($event)"
73173
+ (keydown)="onKeydown($event)"
73174
+ (paste)="onPaste($event)"
73175
+ ></ion-textarea>
73176
+
73177
+ @if (canSend()) {
73178
+ <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73179
+ <ion-icon name="arrow-up-outline" aria-hidden="true" />
73180
+ </button>
73181
+ } @else if (showMic()) {
73182
+ <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
73183
+ <ion-icon name="mic-outline" aria-hidden="true" />
73184
+ </button>
73185
+ }
73186
+ </div>
73187
+ }
73001
73188
  </div>
73002
- `, 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)}.emoji-row{display:flex;gap:4px;flex-wrap:wrap}.emoji{border:none;background:var(--ion-color-step-100, rgba(127, 127, 127, .1));border-radius:10px;padding:4px 8px;font-size:1.25rem;cursor:pointer}.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,.icon-btn.mic:hover{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)}\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"] }] }); }
73189
+ `, 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"] }] }); }
73003
73190
  }
73004
73191
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, decorators: [{
73005
73192
  type: Component,
@@ -73017,52 +73204,84 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
73017
73204
  </div>
73018
73205
  }
73019
73206
 
73020
- @if (emojiOpen()) {
73021
- <div class="emoji-row">
73022
- @for (e of quickEmojis; track e) {
73023
- <button class="emoji" (click)="insertEmoji(e)">{{ e }}</button>
73207
+ @if (pending().length > 0) {
73208
+ <div class="pending-row">
73209
+ @for (att of pending(); track att.id) {
73210
+ @if (att.kind === 'audio') {
73211
+ <div class="audio-chip">
73212
+ <audio controls preload="metadata" [src]="att.url"></audio>
73213
+ <button class="thumb-remove inline" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
73214
+ <ion-icon name="close-outline" aria-hidden="true" />
73215
+ </button>
73216
+ </div>
73217
+ } @else {
73218
+ <div class="thumb" [class.file]="att.kind === 'file'">
73219
+ @if (att.kind === 'image') {
73220
+ <img [src]="att.url" [alt]="att.file.name" />
73221
+ } @else {
73222
+ <ion-icon name="document-outline" aria-hidden="true" />
73223
+ <span class="thumb-name">{{ att.file.name }}</span>
73224
+ }
73225
+ <button class="thumb-remove" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
73226
+ <ion-icon name="close-outline" aria-hidden="true" />
73227
+ </button>
73228
+ </div>
73229
+ }
73024
73230
  }
73025
73231
  </div>
73026
73232
  }
73027
73233
 
73028
- <div class="pill">
73029
- @if (showAttach()) {
73030
- <label class="icon-btn" [attr.aria-label]="t('attach')">
73031
- <ion-icon name="add-outline" aria-hidden="true" />
73032
- <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
73033
- </label>
73034
- }
73035
-
73036
- <ion-textarea
73037
- class="field"
73038
- [placeholder]="placeholderText()"
73039
- [disabled]="disabled()"
73040
- [maxlength]="maxLength()"
73041
- [autoGrow]="true"
73042
- [rows]="1"
73043
- [value]="body()"
73044
- (ionInput)="onInput($event)"
73045
- (keydown)="onKeydown($event)"
73046
- ></ion-textarea>
73047
-
73048
- @if (showEmoji()) {
73049
- <button class="icon-btn" [class.active]="emojiOpen()" [attr.aria-label]="t('emoji')" (click)="toggleEmoji()">
73050
- <ion-icon name="happy-outline" aria-hidden="true" />
73051
- </button>
73052
- }
73053
-
73054
- @if (canSend()) {
73055
- <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73056
- <ion-icon name="arrow-up-outline" aria-hidden="true" />
73234
+ @if (recording()) {
73235
+ <div class="rec-bar">
73236
+ <button class="icon-btn danger" [attr.aria-label]="t('cancel')" (click)="cancelRecording()">
73237
+ <ion-icon name="trash-outline" aria-hidden="true" />
73057
73238
  </button>
73058
- } @else if (showMic()) {
73059
- <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="voice.emit()">
73060
- <ion-icon name="mic-outline" aria-hidden="true" />
73239
+ <span class="rec-dot"></span>
73240
+ <span class="rec-time">{{ recTimeLabel() }}</span>
73241
+ <span class="rec-spacer"></span>
73242
+ <button class="send-btn" [attr.aria-label]="t('done')" (click)="finishRecording()">
73243
+ <ion-icon name="checkmark-outline" aria-hidden="true" />
73061
73244
  </button>
73062
- }
73063
- </div>
73245
+ </div>
73246
+ } @else {
73247
+ <div class="pill">
73248
+ @if (showAttach()) {
73249
+ <label class="icon-btn" [attr.aria-label]="t('attach')">
73250
+ <ion-icon name="add-outline" aria-hidden="true" />
73251
+ <input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
73252
+ </label>
73253
+ }
73254
+
73255
+ <ion-textarea
73256
+ class="field"
73257
+ [placeholder]="placeholderText()"
73258
+ [disabled]="disabled()"
73259
+ [maxlength]="maxLength()"
73260
+ [autoGrow]="true"
73261
+ [rows]="1"
73262
+ [value]="body()"
73263
+ [spellcheck]="false"
73264
+ autocapitalize="sentences"
73265
+ autocorrect="off"
73266
+ enterkeyhint="enter"
73267
+ (ionInput)="onInput($event)"
73268
+ (keydown)="onKeydown($event)"
73269
+ (paste)="onPaste($event)"
73270
+ ></ion-textarea>
73271
+
73272
+ @if (canSend()) {
73273
+ <button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
73274
+ <ion-icon name="arrow-up-outline" aria-hidden="true" />
73275
+ </button>
73276
+ } @else if (showMic()) {
73277
+ <button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
73278
+ <ion-icon name="mic-outline" aria-hidden="true" />
73279
+ </button>
73280
+ }
73281
+ </div>
73282
+ }
73064
73283
  </div>
73065
- `, 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)}.emoji-row{display:flex;gap:4px;flex-wrap:wrap}.emoji{border:none;background:var(--ion-color-step-100, rgba(127, 127, 127, .1));border-radius:10px;padding:4px 8px;font-size:1.25rem;cursor:pointer}.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,.icon-btn.mic:hover{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)}\n"] }]
73284
+ `, 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"] }]
73066
73285
  }], ctorParameters: () => [] });
73067
73286
 
73068
73287
  /**
@@ -73282,7 +73501,6 @@ class ChatWindowComponent {
73282
73501
  this.replyTo = output();
73283
73502
  this.editMessage = output();
73284
73503
  this.typing = output();
73285
- this.attach = output();
73286
73504
  this.voice = output();
73287
73505
  this.msgsEl = viewChild('msgs');
73288
73506
  this.replyingTo = signal(null);
@@ -73366,8 +73584,8 @@ class ChatWindowComponent {
73366
73584
  this.atBottom.set(true);
73367
73585
  this.hasNew.set(false);
73368
73586
  }
73369
- onSend(body) {
73370
- this.sendMessage.emit(body);
73587
+ onSend(event) {
73588
+ this.sendMessage.emit(event);
73371
73589
  this.replyingTo.set(null);
73372
73590
  }
73373
73591
  onAction(event) {
@@ -73395,7 +73613,7 @@ class ChatWindowComponent {
73395
73613
  return this.i18n.t(key, 'ChatWindow');
73396
73614
  }
73397
73615
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
73398
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChatWindowComponent, isStandalone: true, selector: "val-chat-window", inputs: { convId: { classPropertyName: "convId", publicName: "convId", isSignal: true, isRequired: false, transformFunction: null }, messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: false, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, typingUsers: { classPropertyName: "typingUsers", publicName: "typingUsers", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage", loadMore: "loadMore", reactionClick: "reactionClick", deleteMessage: "deleteMessage", replyTo: "replyTo", editMessage: "editMessage", typing: "typing", attach: "attach", voice: "voice" }, viewQueries: [{ propertyName: "msgsEl", first: true, predicate: ["msgs"], descendants: true, isSignal: true }], ngImport: i0, template: `
73616
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChatWindowComponent, isStandalone: true, selector: "val-chat-window", inputs: { convId: { classPropertyName: "convId", publicName: "convId", isSignal: true, isRequired: false, transformFunction: null }, messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: false, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, typingUsers: { classPropertyName: "typingUsers", publicName: "typingUsers", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage", loadMore: "loadMore", reactionClick: "reactionClick", deleteMessage: "deleteMessage", replyTo: "replyTo", editMessage: "editMessage", typing: "typing", voice: "voice" }, viewQueries: [{ propertyName: "msgsEl", first: true, predicate: ["msgs"], descendants: true, isSignal: true }], ngImport: i0, template: `
73399
73617
  <div class="chat">
73400
73618
  <div #msgs class="messages" (scroll)="onScroll()">
73401
73619
  @if (isLoading()) {
@@ -73442,7 +73660,6 @@ class ChatWindowComponent {
73442
73660
  [replyingTo]="replyingTo()"
73443
73661
  (send)="onSend($event)"
73444
73662
  (typing)="typing.emit()"
73445
- (attach)="attach.emit($event)"
73446
73663
  (voice)="voice.emit()"
73447
73664
  (cancelReply)="replyingTo.set(null)"
73448
73665
  />
@@ -73450,7 +73667,7 @@ class ChatWindowComponent {
73450
73667
  <div class="closed-banner">{{ t('conversationClosed') }}</div>
73451
73668
  }
73452
73669
  </div>
73453
- `, isInline: true, styles: [":host{display:block;height:100%}.chat{position:relative;display:flex;flex-direction:column;height:100%;min-height:0}.messages{flex:1;min-height:0;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:0}.state{margin:auto;display:flex;flex-direction:column;align-items:center;gap:8px;color:var(--ion-color-medium, #92949c);font-size:.9375rem;text-align:center;padding:24px}.state.empty ion-icon{font-size:2.5rem;opacity:.5}.date-sep{position:sticky;top:4px;z-index:1;display:flex;justify-content:center;margin:8px 0}.date-sep span{font-size:.75rem;color:var(--ion-color-medium, #92949c);background:var(--ion-color-step-100, rgba(127, 127, 127, .14));padding:3px 12px;border-radius:999px;backdrop-filter:blur(4px)}.scroll-down{position:absolute;right:14px;bottom:84px;width:40px;height:40px;border-radius:50%;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));color:var(--ion-color-medium, #92949c);box-shadow:0 2px 10px #00000026;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:1.25rem;z-index:3}.scroll-down .dot{position:absolute;top:0;right:0;width:12px;height:12px;border-radius:50%;background:var(--ion-color-primary);border:2px solid var(--ion-card-background, #fff)}.closed-banner{text-align:center;padding:14px;color:var(--ion-color-medium, #92949c);font-size:.875rem;border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}\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"] }, { kind: "component", type: MessageBubbleComponent, selector: "val-message-bubble", inputs: ["msg", "showAvatar", "showName", "tail", "locale"], outputs: ["action"] }, { kind: "component", type: ChatComposerComponent, selector: "val-chat-composer", inputs: ["placeholder", "disabled", "maxLength", "replyingTo", "showAttach", "showEmoji", "showMic"], outputs: ["send", "typing", "attach", "voice", "cancelReply"] }, { kind: "component", type: TypingIndicatorComponent, selector: "val-typing-indicator", inputs: ["typingUsers"] }] }); }
73670
+ `, isInline: true, styles: [":host{display:block;height:100%}.chat{position:relative;display:flex;flex-direction:column;height:100%;min-height:0}.messages{flex:1;min-height:0;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:0}.state{margin:auto;display:flex;flex-direction:column;align-items:center;gap:8px;color:var(--ion-color-medium, #92949c);font-size:.9375rem;text-align:center;padding:24px}.state.empty ion-icon{font-size:2.5rem;opacity:.5}.date-sep{position:sticky;top:4px;z-index:1;display:flex;justify-content:center;margin:8px 0}.date-sep span{font-size:.75rem;color:var(--ion-color-medium, #92949c);background:var(--ion-color-step-100, rgba(127, 127, 127, .14));padding:3px 12px;border-radius:999px;backdrop-filter:blur(4px)}.scroll-down{position:absolute;right:14px;bottom:84px;width:40px;height:40px;border-radius:50%;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));color:var(--ion-color-medium, #92949c);box-shadow:0 2px 10px #00000026;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:1.25rem;z-index:3}.scroll-down .dot{position:absolute;top:0;right:0;width:12px;height:12px;border-radius:50%;background:var(--ion-color-primary);border:2px solid var(--ion-card-background, #fff)}.closed-banner{text-align:center;padding:14px;color:var(--ion-color-medium, #92949c);font-size:.875rem;border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}\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"] }, { kind: "component", type: MessageBubbleComponent, selector: "val-message-bubble", inputs: ["msg", "showAvatar", "showName", "tail", "locale"], outputs: ["action"] }, { kind: "component", type: ChatComposerComponent, selector: "val-chat-composer", inputs: ["placeholder", "disabled", "maxLength", "replyingTo", "showAttach", "showMic"], outputs: ["send", "typing", "voice", "cancelReply"] }, { kind: "component", type: TypingIndicatorComponent, selector: "val-typing-indicator", inputs: ["typingUsers"] }] }); }
73454
73671
  }
73455
73672
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, decorators: [{
73456
73673
  type: Component,
@@ -73501,7 +73718,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
73501
73718
  [replyingTo]="replyingTo()"
73502
73719
  (send)="onSend($event)"
73503
73720
  (typing)="typing.emit()"
73504
- (attach)="attach.emit($event)"
73505
73721
  (voice)="voice.emit()"
73506
73722
  (cancelReply)="replyingTo.set(null)"
73507
73723
  />
@@ -73615,11 +73831,13 @@ class ThreadPanelComponent {
73615
73831
  this.conversationSvc.stopTyping(conv.convId, orgId, appId);
73616
73832
  }
73617
73833
  }
73618
- onSend(body) {
73834
+ onSend(event) {
73619
73835
  const conv = this.conv();
73620
73836
  if (!conv || !this.conversationSvc)
73621
73837
  return;
73622
- this.conversationSvc.sendMessage(conv.convId, body).subscribe();
73838
+ // thread-panel no tiene pipeline de subida de adjuntos; solo envía el texto.
73839
+ if (event.text)
73840
+ this.conversationSvc.sendMessage(conv.convId, event.text).subscribe();
73623
73841
  }
73624
73842
  onReactionClick(event) {
73625
73843
  // Reacciones se implementan a nivel de app consumer
@@ -73668,7 +73886,7 @@ class ThreadPanelComponent {
73668
73886
  </div>
73669
73887
  }
73670
73888
  </div>
73671
- `, isInline: true, styles: [".thread-panel{display:flex;flex-direction:column;height:100%;background:var(--ion-background-color, #f4f5f8)}.thread-header{padding:12px 16px;border-bottom:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff))}.thread-header .thread-title{font-size:1rem;font-weight:600;color:var(--ion-text-color, #000);margin:0}.thread-loading{display:flex;align-items:center;justify-content:center;flex:1;gap:10px;color:var(--ion-color-medium, #92949c);font-size:.875rem}.thread-chat{flex:1;overflow:hidden;display:flex;flex-direction:column}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: ChatWindowComponent, selector: "val-chat-window", inputs: ["convId", "messages", "currentUserId", "isOpen", "typingUsers", "isLoading"], outputs: ["sendMessage", "loadMore", "reactionClick", "deleteMessage", "replyTo", "editMessage", "typing", "attach", "voice"] }] }); }
73889
+ `, isInline: true, styles: [".thread-panel{display:flex;flex-direction:column;height:100%;background:var(--ion-background-color, #f4f5f8)}.thread-header{padding:12px 16px;border-bottom:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff))}.thread-header .thread-title{font-size:1rem;font-weight:600;color:var(--ion-text-color, #000);margin:0}.thread-loading{display:flex;align-items:center;justify-content:center;flex:1;gap:10px;color:var(--ion-color-medium, #92949c);font-size:.875rem}.thread-chat{flex:1;overflow:hidden;display:flex;flex-direction:column}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: ChatWindowComponent, selector: "val-chat-window", inputs: ["convId", "messages", "currentUserId", "isOpen", "typingUsers", "isLoading"], outputs: ["sendMessage", "loadMore", "reactionClick", "deleteMessage", "replyTo", "editMessage", "typing", "voice"] }] }); }
73672
73890
  }
73673
73891
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThreadPanelComponent, decorators: [{
73674
73892
  type: Component,