valtech-components 4.0.229 → 4.0.230

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.229';
59
+ const VERSION = '4.0.230';
60
60
 
61
61
  // Control de estado de refresco (singleton a nivel de módulo)
62
62
  let isRefreshing = false;
@@ -72822,13 +72822,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
72822
72822
  type: Output
72823
72823
  }] } });
72824
72824
 
72825
- addIcons({ addOutline, closeOutline, documentOutline, micOutline, arrowUpOutline, trashOutline });
72825
+ addIcons({ addOutline, checkmarkOutline, closeOutline, documentOutline, micOutline, arrowUpOutline, trashOutline });
72826
72826
  const CHAT_COMPOSER_I18N = {
72827
72827
  es: {
72828
72828
  placeholder: 'Escribe un mensaje',
72829
72829
  send: 'Enviar',
72830
72830
  attach: 'Adjuntar',
72831
72831
  voice: 'Mensaje de voz',
72832
+ done: 'Listo',
72832
72833
  replyingTo: 'Respondiendo a',
72833
72834
  cancel: 'Cancelar',
72834
72835
  remove: 'Quitar',
@@ -72838,6 +72839,7 @@ const CHAT_COMPOSER_I18N = {
72838
72839
  send: 'Send',
72839
72840
  attach: 'Attach',
72840
72841
  voice: 'Voice message',
72842
+ done: 'Done',
72841
72843
  replyingTo: 'Replying to',
72842
72844
  cancel: 'Cancel',
72843
72845
  remove: 'Remove',
@@ -72863,7 +72865,6 @@ class ChatComposerComponent {
72863
72865
  this.showMic = input(true);
72864
72866
  this.send = output();
72865
72867
  this.typing = output();
72866
- this.attach = output();
72867
72868
  this.voice = output();
72868
72869
  this.cancelReply = output();
72869
72870
  this.body = signal('');
@@ -72906,19 +72907,16 @@ class ChatComposerComponent {
72906
72907
  onSend() {
72907
72908
  if (!this.canSend())
72908
72909
  return;
72909
- // Primero los adjuntos pendientes (cada uno se sube y manda en el consumer),
72910
- // luego el texto como mensaje aparte si lo hay.
72911
- const files = this.pending();
72912
- for (const att of files) {
72913
- this.attach.emit(att.file);
72910
+ // Un solo evento: texto (caption) + todos los adjuntos staged. El consumer
72911
+ // los sube y manda como UN mensaje con body + attachments.
72912
+ const files = this.pending().map(p => p.file);
72913
+ const text = this.body().trim();
72914
+ this.send.emit({ text, files });
72915
+ for (const att of this.pending()) {
72914
72916
  if (att.url)
72915
72917
  URL.revokeObjectURL(att.url);
72916
72918
  }
72917
72919
  this.pending.set([]);
72918
- const trimmed = this.body().trim();
72919
- if (trimmed && trimmed.length <= this.maxLength()) {
72920
- this.send.emit(trimmed);
72921
- }
72922
72920
  this.body.set('');
72923
72921
  }
72924
72922
  onFile(event) {
@@ -72945,9 +72943,13 @@ class ChatComposerComponent {
72945
72943
  }
72946
72944
  }
72947
72945
  addPending(file) {
72948
- const isImage = file.type.startsWith('image/');
72949
- const url = isImage ? URL.createObjectURL(file) : undefined;
72950
- this.pending.update(arr => [...arr, { id: this.pendingId++, file, url, isImage }]);
72946
+ const kind = file.type.startsWith('image/')
72947
+ ? 'image'
72948
+ : file.type.startsWith('audio/')
72949
+ ? 'audio'
72950
+ : 'file';
72951
+ const url = kind === 'file' ? undefined : URL.createObjectURL(file);
72952
+ this.pending.update(arr => [...arr, { id: this.pendingId++, file, url, kind }]);
72951
72953
  }
72952
72954
  removePending(id) {
72953
72955
  this.pending.update(arr => {
@@ -73002,7 +73004,8 @@ class ChatComposerComponent {
73002
73004
  this.cleanupRecording();
73003
73005
  }
73004
73006
  }
73005
- stopAndSend() {
73007
+ /** Para la grabación y la stagea como adjunto pendiente (con preview de audio). */
73008
+ finishRecording() {
73006
73009
  const rec = this.recorder;
73007
73010
  if (!rec) {
73008
73011
  this.cleanupRecording();
@@ -73014,7 +73017,7 @@ class ChatComposerComponent {
73014
73017
  const ext = type.includes('mp4') ? 'm4a' : 'webm';
73015
73018
  const file = new File([blob], `audio-${this.recSeconds()}s.${ext}`, { type });
73016
73019
  if (blob.size > 0)
73017
- this.attach.emit(file);
73020
+ this.addPending(file);
73018
73021
  this.cleanupRecording();
73019
73022
  };
73020
73023
  rec.stop();
@@ -73054,7 +73057,7 @@ class ChatComposerComponent {
73054
73057
  return this.i18n.t(key, 'ChatComposer');
73055
73058
  }
73056
73059
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
73057
- 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", attach: "attach", voice: "voice", cancelReply: "cancelReply" }, ngImport: i0, template: `
73060
+ 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: `
73058
73061
  <div class="composer">
73059
73062
  @if (replyingTo()) {
73060
73063
  <div class="reply-bar">
@@ -73071,17 +73074,26 @@ class ChatComposerComponent {
73071
73074
  @if (pending().length > 0) {
73072
73075
  <div class="pending-row">
73073
73076
  @for (att of pending(); track att.id) {
73074
- <div class="thumb" [class.file]="!att.isImage">
73075
- @if (att.isImage) {
73076
- <img [src]="att.url" [alt]="att.file.name" />
73077
- } @else {
73078
- <ion-icon name="document-outline" aria-hidden="true" />
73079
- <span class="thumb-name">{{ att.file.name }}</span>
73080
- }
73081
- <button class="thumb-remove" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
73082
- <ion-icon name="close-outline" aria-hidden="true" />
73083
- </button>
73084
- </div>
73077
+ @if (att.kind === 'audio') {
73078
+ <div class="audio-chip">
73079
+ <audio controls preload="metadata" [src]="att.url"></audio>
73080
+ <button class="thumb-remove inline" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
73081
+ <ion-icon name="close-outline" aria-hidden="true" />
73082
+ </button>
73083
+ </div>
73084
+ } @else {
73085
+ <div class="thumb" [class.file]="att.kind === 'file'">
73086
+ @if (att.kind === 'image') {
73087
+ <img [src]="att.url" [alt]="att.file.name" />
73088
+ } @else {
73089
+ <ion-icon name="document-outline" aria-hidden="true" />
73090
+ <span class="thumb-name">{{ att.file.name }}</span>
73091
+ }
73092
+ <button class="thumb-remove" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
73093
+ <ion-icon name="close-outline" aria-hidden="true" />
73094
+ </button>
73095
+ </div>
73096
+ }
73085
73097
  }
73086
73098
  </div>
73087
73099
  }
@@ -73094,8 +73106,8 @@ class ChatComposerComponent {
73094
73106
  <span class="rec-dot"></span>
73095
73107
  <span class="rec-time">{{ recTimeLabel() }}</span>
73096
73108
  <span class="rec-spacer"></span>
73097
- <button class="send-btn" [attr.aria-label]="t('send')" (click)="stopAndSend()">
73098
- <ion-icon name="arrow-up-outline" aria-hidden="true" />
73109
+ <button class="send-btn" [attr.aria-label]="t('done')" (click)="finishRecording()">
73110
+ <ion-icon name="checkmark-outline" aria-hidden="true" />
73099
73111
  </button>
73100
73112
  </div>
73101
73113
  } @else {
@@ -73136,7 +73148,7 @@ class ChatComposerComponent {
73136
73148
  </div>
73137
73149
  }
73138
73150
  </div>
73139
- `, 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)}.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"] }] }); }
73151
+ `, 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"] }] }); }
73140
73152
  }
73141
73153
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, decorators: [{
73142
73154
  type: Component,
@@ -73157,17 +73169,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
73157
73169
  @if (pending().length > 0) {
73158
73170
  <div class="pending-row">
73159
73171
  @for (att of pending(); track att.id) {
73160
- <div class="thumb" [class.file]="!att.isImage">
73161
- @if (att.isImage) {
73162
- <img [src]="att.url" [alt]="att.file.name" />
73163
- } @else {
73164
- <ion-icon name="document-outline" aria-hidden="true" />
73165
- <span class="thumb-name">{{ att.file.name }}</span>
73166
- }
73167
- <button class="thumb-remove" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
73168
- <ion-icon name="close-outline" aria-hidden="true" />
73169
- </button>
73170
- </div>
73172
+ @if (att.kind === 'audio') {
73173
+ <div class="audio-chip">
73174
+ <audio controls preload="metadata" [src]="att.url"></audio>
73175
+ <button class="thumb-remove inline" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
73176
+ <ion-icon name="close-outline" aria-hidden="true" />
73177
+ </button>
73178
+ </div>
73179
+ } @else {
73180
+ <div class="thumb" [class.file]="att.kind === 'file'">
73181
+ @if (att.kind === 'image') {
73182
+ <img [src]="att.url" [alt]="att.file.name" />
73183
+ } @else {
73184
+ <ion-icon name="document-outline" aria-hidden="true" />
73185
+ <span class="thumb-name">{{ att.file.name }}</span>
73186
+ }
73187
+ <button class="thumb-remove" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
73188
+ <ion-icon name="close-outline" aria-hidden="true" />
73189
+ </button>
73190
+ </div>
73191
+ }
73171
73192
  }
73172
73193
  </div>
73173
73194
  }
@@ -73180,8 +73201,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
73180
73201
  <span class="rec-dot"></span>
73181
73202
  <span class="rec-time">{{ recTimeLabel() }}</span>
73182
73203
  <span class="rec-spacer"></span>
73183
- <button class="send-btn" [attr.aria-label]="t('send')" (click)="stopAndSend()">
73184
- <ion-icon name="arrow-up-outline" aria-hidden="true" />
73204
+ <button class="send-btn" [attr.aria-label]="t('done')" (click)="finishRecording()">
73205
+ <ion-icon name="checkmark-outline" aria-hidden="true" />
73185
73206
  </button>
73186
73207
  </div>
73187
73208
  } @else {
@@ -73222,7 +73243,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
73222
73243
  </div>
73223
73244
  }
73224
73245
  </div>
73225
- `, 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)}.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"] }]
73246
+ `, 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"] }]
73226
73247
  }], ctorParameters: () => [] });
73227
73248
 
73228
73249
  /**
@@ -73442,7 +73463,6 @@ class ChatWindowComponent {
73442
73463
  this.replyTo = output();
73443
73464
  this.editMessage = output();
73444
73465
  this.typing = output();
73445
- this.attach = output();
73446
73466
  this.voice = output();
73447
73467
  this.msgsEl = viewChild('msgs');
73448
73468
  this.replyingTo = signal(null);
@@ -73526,8 +73546,8 @@ class ChatWindowComponent {
73526
73546
  this.atBottom.set(true);
73527
73547
  this.hasNew.set(false);
73528
73548
  }
73529
- onSend(body) {
73530
- this.sendMessage.emit(body);
73549
+ onSend(event) {
73550
+ this.sendMessage.emit(event);
73531
73551
  this.replyingTo.set(null);
73532
73552
  }
73533
73553
  onAction(event) {
@@ -73555,7 +73575,7 @@ class ChatWindowComponent {
73555
73575
  return this.i18n.t(key, 'ChatWindow');
73556
73576
  }
73557
73577
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
73558
- 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: `
73578
+ 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: `
73559
73579
  <div class="chat">
73560
73580
  <div #msgs class="messages" (scroll)="onScroll()">
73561
73581
  @if (isLoading()) {
@@ -73602,7 +73622,6 @@ class ChatWindowComponent {
73602
73622
  [replyingTo]="replyingTo()"
73603
73623
  (send)="onSend($event)"
73604
73624
  (typing)="typing.emit()"
73605
- (attach)="attach.emit($event)"
73606
73625
  (voice)="voice.emit()"
73607
73626
  (cancelReply)="replyingTo.set(null)"
73608
73627
  />
@@ -73610,7 +73629,7 @@ class ChatWindowComponent {
73610
73629
  <div class="closed-banner">{{ t('conversationClosed') }}</div>
73611
73630
  }
73612
73631
  </div>
73613
- `, 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", "attach", "voice", "cancelReply"] }, { kind: "component", type: TypingIndicatorComponent, selector: "val-typing-indicator", inputs: ["typingUsers"] }] }); }
73632
+ `, 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"] }] }); }
73614
73633
  }
73615
73634
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, decorators: [{
73616
73635
  type: Component,
@@ -73661,7 +73680,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
73661
73680
  [replyingTo]="replyingTo()"
73662
73681
  (send)="onSend($event)"
73663
73682
  (typing)="typing.emit()"
73664
- (attach)="attach.emit($event)"
73665
73683
  (voice)="voice.emit()"
73666
73684
  (cancelReply)="replyingTo.set(null)"
73667
73685
  />
@@ -73775,11 +73793,13 @@ class ThreadPanelComponent {
73775
73793
  this.conversationSvc.stopTyping(conv.convId, orgId, appId);
73776
73794
  }
73777
73795
  }
73778
- onSend(body) {
73796
+ onSend(event) {
73779
73797
  const conv = this.conv();
73780
73798
  if (!conv || !this.conversationSvc)
73781
73799
  return;
73782
- this.conversationSvc.sendMessage(conv.convId, body).subscribe();
73800
+ // thread-panel no tiene pipeline de subida de adjuntos; solo envía el texto.
73801
+ if (event.text)
73802
+ this.conversationSvc.sendMessage(conv.convId, event.text).subscribe();
73783
73803
  }
73784
73804
  onReactionClick(event) {
73785
73805
  // Reacciones se implementan a nivel de app consumer
@@ -73828,7 +73848,7 @@ class ThreadPanelComponent {
73828
73848
  </div>
73829
73849
  }
73830
73850
  </div>
73831
- `, 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"] }] }); }
73851
+ `, 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"] }] }); }
73832
73852
  }
73833
73853
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThreadPanelComponent, decorators: [{
73834
73854
  type: Component,