@forgeroute/ui 0.4.43 → 0.4.44

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.
@@ -4,7 +4,7 @@ import * as i1 from "@angular/forms";
4
4
  import { FormsModule } from "@angular/forms";
5
5
  import { HttpClient, HttpErrorResponse } from "@angular/common/http";
6
6
  import { takeUntilDestroyed, toObservable } from "@angular/core/rxjs-interop";
7
- import { AlertComponent, ButtonComponent, CheckboxComponent, ChipComponent, ComboboxComponent, ContactPickerComponent, DatetimeInputComponent, ErrorComponent, FORGEROUTE_CONFIG, FormRowComponent, FormSectionComponent, HintComponent, PillTabsComponent, RadioGroupComponent, SelectComponent, SidePanelComponent, TextInputComponent, TextareaInputComponent, ToastService, ToggleBadgeComponent, UiFieldValidity, createFieldReveal, getErrorMessage, requiredIssue } from "@forgeroute/ui";
7
+ import { AlertComponent, ButtonComponent, CheckboxComponent, ChipComponent, ComboboxComponent, ContactPickerComponent, DatetimeInputComponent, ErrorComponent, FORGEROUTE_CONFIG, FormRowComponent, FormSectionComponent, HintComponent, PillTabsComponent, RadioGroupComponent, RichTextEditorComponent, SelectComponent, SidePanelComponent, TextInputComponent, TextareaInputComponent, ToastService, ToggleBadgeComponent, UiFieldValidity, createFieldReveal, getErrorMessage, requiredIssue } from "@forgeroute/ui";
8
8
  import { Subject, catchError, debounceTime, defer, distinctUntilChanged, map, of, switchMap, take } from "rxjs";
9
9
  let nextVideoPickerId = 0;
10
10
  var VideoPickerComponent = class VideoPickerComponent {
@@ -5491,8 +5491,12 @@ var SendInAppComponent = class SendInAppComponent {
5491
5491
  constructor() {
5492
5492
  this.destroyRef = inject(DestroyRef);
5493
5493
  this.toast = inject(ToastService);
5494
+ this.cdr = inject(ChangeDetectorRef);
5494
5495
  this.contactName = input("", ...ngDevMode ? [{ debugName: "contactName" }] : /* istanbul ignore next */ []);
5495
5496
  this.contactId = input("", ...ngDevMode ? [{ debugName: "contactId" }] : /* istanbul ignore next */ []);
5497
+ this.contactEmail = input("", ...ngDevMode ? [{ debugName: "contactEmail" }] : /* istanbul ignore next */ []);
5498
+ this.allowAttachments = input(false, ...ngDevMode ? [{ debugName: "allowAttachments" }] : /* istanbul ignore next */ []);
5499
+ this.richText = input(false, ...ngDevMode ? [{ debugName: "richText" }] : /* istanbul ignore next */ []);
5496
5500
  this.defaultSubject = input("", ...ngDevMode ? [{ debugName: "defaultSubject" }] : /* istanbul ignore next */ []);
5497
5501
  this.defaultBody = input("", ...ngDevMode ? [{ debugName: "defaultBody" }] : /* istanbul ignore next */ []);
5498
5502
  this.showSubject = input(true, ...ngDevMode ? [{ debugName: "showSubject" }] : /* istanbul ignore next */ []);
@@ -5505,18 +5509,50 @@ var SendInAppComponent = class SendInAppComponent {
5505
5509
  this.sending = signal(false, ...ngDevMode ? [{ debugName: "sending" }] : /* istanbul ignore next */ []);
5506
5510
  this.subject = "";
5507
5511
  this.body = "";
5512
+ this.attachments = [];
5508
5513
  }
5509
5514
  ngOnInit() {
5510
5515
  this.subject = this.defaultSubject();
5511
5516
  this.body = this.defaultBody();
5512
5517
  this.notifyValid();
5513
5518
  }
5519
+ bodyText() {
5520
+ if (!this.richText()) return this.body;
5521
+ return (new DOMParser().parseFromString(this.body || "", "text/html").body.textContent ?? "").replace(/\u00a0/g, " ");
5522
+ }
5523
+ richTooLong() {
5524
+ const max = this.maxLength();
5525
+ return this.richText() && max != null && this.body.length > max;
5526
+ }
5514
5527
  get canSend() {
5515
- if (!this.body.trim() || this.showSubject() && !this.subject.trim()) return false;
5528
+ if (!this.bodyText().trim() || this.showSubject() && !this.subject.trim()) return false;
5516
5529
  if (this.showSubject() && this.subject.length > MAX_SUBJECT) return false;
5517
5530
  const max = this.maxLength();
5518
5531
  return max == null || this.body.length <= max;
5519
5532
  }
5533
+ onFilesSelected(event) {
5534
+ const input = event.target;
5535
+ const files = input.files ? Array.from(input.files) : [];
5536
+ for (const file of files) {
5537
+ const reader = new FileReader();
5538
+ reader.onload = () => {
5539
+ const base64 = String(reader.result).split(",")[1] ?? "";
5540
+ const sizeLabel = file.size < 1048576 ? `${Math.round(file.size / 1024)} KB` : `${(file.size / 1048576).toFixed(1)} MB`;
5541
+ this.attachments = [...this.attachments, {
5542
+ filename: file.name,
5543
+ content: base64,
5544
+ contentType: file.type || "application/octet-stream",
5545
+ sizeLabel
5546
+ }];
5547
+ this.cdr.markForCheck();
5548
+ };
5549
+ reader.readAsDataURL(file);
5550
+ }
5551
+ input.value = "";
5552
+ }
5553
+ removeAttachment(index) {
5554
+ this.attachments = this.attachments.filter((_, i) => i !== index);
5555
+ }
5520
5556
  notifyValid() {
5521
5557
  this.validChange.emit(!this.sending() && !!this.sendFn() && this.canSend);
5522
5558
  }
@@ -5530,11 +5566,18 @@ var SendInAppComponent = class SendInAppComponent {
5530
5566
  contactId: this.contactId() || void 0,
5531
5567
  body: this.body.trim()
5532
5568
  };
5569
+ if (this.richText()) payload.bodyIsHtml = true;
5570
+ if (this.attachments.length) payload.attachments = this.attachments.map(({ filename, content, contentType }) => ({
5571
+ filename,
5572
+ content,
5573
+ contentType
5574
+ }));
5533
5575
  if (this.showSubject()) payload.subject = this.subject.trim();
5534
5576
  send(payload).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
5535
5577
  next: () => {
5536
5578
  this.toast.show("Message sent");
5537
5579
  this.sending.set(false);
5580
+ this.attachments = [];
5538
5581
  this.sent.emit();
5539
5582
  },
5540
5583
  error: (err) => {
@@ -5576,6 +5619,27 @@ var SendInAppComponent = class SendInAppComponent {
5576
5619
  isRequired: false,
5577
5620
  transformFunction: null
5578
5621
  },
5622
+ contactEmail: {
5623
+ classPropertyName: "contactEmail",
5624
+ publicName: "contactEmail",
5625
+ isSignal: true,
5626
+ isRequired: false,
5627
+ transformFunction: null
5628
+ },
5629
+ allowAttachments: {
5630
+ classPropertyName: "allowAttachments",
5631
+ publicName: "allowAttachments",
5632
+ isSignal: true,
5633
+ isRequired: false,
5634
+ transformFunction: null
5635
+ },
5636
+ richText: {
5637
+ classPropertyName: "richText",
5638
+ publicName: "richText",
5639
+ isSignal: true,
5640
+ isRequired: false,
5641
+ transformFunction: null
5642
+ },
5579
5643
  defaultSubject: {
5580
5644
  classPropertyName: "defaultSubject",
5581
5645
  publicName: "defaultSubject",
@@ -5622,6 +5686,7 @@ var SendInAppComponent = class SendInAppComponent {
5622
5686
  <span class="sm-group-label">To</span>
5623
5687
  <div class="sm-to-card">
5624
5688
  <span class="sm-to-name">{{ contactName() }}</span>
5689
+ @if (contactEmail()) { <span class="sm-to-email">{{ contactEmail() }}</span> }
5625
5690
  </div>
5626
5691
  </div>
5627
5692
  @if (showSubject()) {
@@ -5633,7 +5698,16 @@ var SendInAppComponent = class SendInAppComponent {
5633
5698
  [(ngModel)]="subject"
5634
5699
  (ngModelChange)="notifyValid()" />
5635
5700
  }
5636
- @if (limit(); as max) {
5701
+ @if (richText()) {
5702
+ <ui-rich-text-editor
5703
+ label="Message"
5704
+ [required]="true"
5705
+ placeholder="Write your message..."
5706
+ [minHeight]="180"
5707
+ [error]="richTooLong() ? 'Too long (max ' + limit() + ' characters).' : ''"
5708
+ [(ngModel)]="body"
5709
+ (ngModelChange)="notifyValid()" />
5710
+ } @else if (limit(); as max) {
5637
5711
  <ui-textarea-input
5638
5712
  label="Message"
5639
5713
  [required]="true"
@@ -5651,9 +5725,27 @@ var SendInAppComponent = class SendInAppComponent {
5651
5725
  [(ngModel)]="body"
5652
5726
  (ngModelChange)="notifyValid()" />
5653
5727
  }
5728
+ @if (allowAttachments()) {
5729
+ <div class="sm-group" data-testid="in-app-attachments">
5730
+ <span class="sm-group-label">Attachments</span>
5731
+ @if (attachments.length > 0) {
5732
+ <div class="attach-chips">
5733
+ @for (a of attachments; track $index; let i = $index) {
5734
+ <div class="attach-chip">
5735
+ <span class="attach-name">{{ a.filename }}</span>
5736
+ <span class="attach-size">{{ a.sizeLabel }}</span>
5737
+ <button class="attach-remove" type="button" (click)="removeAttachment(i)" [attr.aria-label]="'Remove ' + a.filename">&times;</button>
5738
+ </div>
5739
+ }
5740
+ </div>
5741
+ }
5742
+ <input #fileInput type="file" multiple hidden data-testid="in-app-file-input" (change)="onFilesSelected($event)" />
5743
+ <button type="button" class="attach-add-btn" (click)="fileInput.click()">Attach Files</button>
5744
+ </div>
5745
+ }
5654
5746
  `,
5655
5747
  isInline: true,
5656
- styles: [":host{display:flex;flex-direction:column;gap:14px}.sm-group-label{display:block;font-size:.8125rem;font-weight:600;color:#334155;margin-bottom:6px}.sm-to-card{border:1px solid #e2e8f0;border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:3px}.sm-to-name{font-size:.9375rem;font-weight:600;color:#1e293b;min-width:0;overflow-wrap:anywhere}\n"],
5748
+ styles: [":host{display:flex;flex-direction:column;gap:14px}.sm-group-label{display:block;font-size:.8125rem;font-weight:600;color:#334155;margin-bottom:6px}.sm-to-card{border:1px solid #e2e8f0;border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:3px}.sm-to-name{font-size:.9375rem;font-weight:600;color:#1e293b;min-width:0;overflow-wrap:anywhere}.sm-to-email{font-size:.8125rem;color:#64748b;min-width:0;overflow-wrap:anywhere}.attach-chips{display:flex;flex-direction:column;gap:6px;margin-bottom:8px}.attach-chip{display:flex;align-items:center;gap:8px;padding:7px 10px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px}.attach-name{font-size:.8125rem;font-weight:500;color:#1e293b;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.attach-size{font-size:.75rem;color:#94a3b8;flex-shrink:0}.attach-remove{position:relative;width:18px;height:18px;border:none;border-radius:50%;background:#e2e8f0;color:#475569;font-size:14px;line-height:1;cursor:pointer;padding:0;flex-shrink:0}.attach-remove:hover{background:#cbd5e1}.attach-add-btn{display:inline-flex;align-items:center;gap:6px;padding:7px 12px;border:1px dashed #cbd5e1;border-radius:8px;background:#fff;color:#334155;font-size:.8125rem;font-weight:500;cursor:pointer}.attach-add-btn:hover{background:#f8fafc;border-color:#94a3b8}@media(pointer:coarse){.attach-remove:after{content:\"\";position:absolute;inset:-13px}.attach-add-btn{min-height:44px}}\n"],
5657
5749
  dependencies: [
5658
5750
  {
5659
5751
  kind: "ngmodule",
@@ -5748,6 +5840,24 @@ var SendInAppComponent = class SendInAppComponent {
5748
5840
  "disabled"
5749
5841
  ],
5750
5842
  outputs: ["invalidChange"]
5843
+ },
5844
+ {
5845
+ kind: "component",
5846
+ type: RichTextEditorComponent,
5847
+ selector: "ui-rich-text-editor",
5848
+ inputs: [
5849
+ "label",
5850
+ "ariaLabel",
5851
+ "placeholder",
5852
+ "required",
5853
+ "readonly",
5854
+ "hint",
5855
+ "error",
5856
+ "minHeight",
5857
+ "maxHeight",
5858
+ "hide",
5859
+ "disabled"
5860
+ ]
5751
5861
  }
5752
5862
  ],
5753
5863
  changeDetection: i0.ChangeDetectionStrategy.OnPush
@@ -5768,13 +5878,15 @@ i0.ɵɵngDeclareClassMetadata({
5768
5878
  imports: [
5769
5879
  FormsModule,
5770
5880
  TextInputComponent,
5771
- TextareaInputComponent
5881
+ TextareaInputComponent,
5882
+ RichTextEditorComponent
5772
5883
  ],
5773
5884
  template: `
5774
5885
  <div class="sm-group">
5775
5886
  <span class="sm-group-label">To</span>
5776
5887
  <div class="sm-to-card">
5777
5888
  <span class="sm-to-name">{{ contactName() }}</span>
5889
+ @if (contactEmail()) { <span class="sm-to-email">{{ contactEmail() }}</span> }
5778
5890
  </div>
5779
5891
  </div>
5780
5892
  @if (showSubject()) {
@@ -5786,7 +5898,16 @@ i0.ɵɵngDeclareClassMetadata({
5786
5898
  [(ngModel)]="subject"
5787
5899
  (ngModelChange)="notifyValid()" />
5788
5900
  }
5789
- @if (limit(); as max) {
5901
+ @if (richText()) {
5902
+ <ui-rich-text-editor
5903
+ label="Message"
5904
+ [required]="true"
5905
+ placeholder="Write your message..."
5906
+ [minHeight]="180"
5907
+ [error]="richTooLong() ? 'Too long (max ' + limit() + ' characters).' : ''"
5908
+ [(ngModel)]="body"
5909
+ (ngModelChange)="notifyValid()" />
5910
+ } @else if (limit(); as max) {
5790
5911
  <ui-textarea-input
5791
5912
  label="Message"
5792
5913
  [required]="true"
@@ -5804,8 +5925,26 @@ i0.ɵɵngDeclareClassMetadata({
5804
5925
  [(ngModel)]="body"
5805
5926
  (ngModelChange)="notifyValid()" />
5806
5927
  }
5928
+ @if (allowAttachments()) {
5929
+ <div class="sm-group" data-testid="in-app-attachments">
5930
+ <span class="sm-group-label">Attachments</span>
5931
+ @if (attachments.length > 0) {
5932
+ <div class="attach-chips">
5933
+ @for (a of attachments; track $index; let i = $index) {
5934
+ <div class="attach-chip">
5935
+ <span class="attach-name">{{ a.filename }}</span>
5936
+ <span class="attach-size">{{ a.sizeLabel }}</span>
5937
+ <button class="attach-remove" type="button" (click)="removeAttachment(i)" [attr.aria-label]="'Remove ' + a.filename">&times;</button>
5938
+ </div>
5939
+ }
5940
+ </div>
5941
+ }
5942
+ <input #fileInput type="file" multiple hidden data-testid="in-app-file-input" (change)="onFilesSelected($event)" />
5943
+ <button type="button" class="attach-add-btn" (click)="fileInput.click()">Attach Files</button>
5944
+ </div>
5945
+ }
5807
5946
  `,
5808
- styles: [":host{display:flex;flex-direction:column;gap:14px}.sm-group-label{display:block;font-size:.8125rem;font-weight:600;color:#334155;margin-bottom:6px}.sm-to-card{border:1px solid #e2e8f0;border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:3px}.sm-to-name{font-size:.9375rem;font-weight:600;color:#1e293b;min-width:0;overflow-wrap:anywhere}\n"]
5947
+ styles: [":host{display:flex;flex-direction:column;gap:14px}.sm-group-label{display:block;font-size:.8125rem;font-weight:600;color:#334155;margin-bottom:6px}.sm-to-card{border:1px solid #e2e8f0;border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:3px}.sm-to-name{font-size:.9375rem;font-weight:600;color:#1e293b;min-width:0;overflow-wrap:anywhere}.sm-to-email{font-size:.8125rem;color:#64748b;min-width:0;overflow-wrap:anywhere}.attach-chips{display:flex;flex-direction:column;gap:6px;margin-bottom:8px}.attach-chip{display:flex;align-items:center;gap:8px;padding:7px 10px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px}.attach-name{font-size:.8125rem;font-weight:500;color:#1e293b;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.attach-size{font-size:.75rem;color:#94a3b8;flex-shrink:0}.attach-remove{position:relative;width:18px;height:18px;border:none;border-radius:50%;background:#e2e8f0;color:#475569;font-size:14px;line-height:1;cursor:pointer;padding:0;flex-shrink:0}.attach-remove:hover{background:#cbd5e1}.attach-add-btn{display:inline-flex;align-items:center;gap:6px;padding:7px 12px;border:1px dashed #cbd5e1;border-radius:8px;background:#fff;color:#334155;font-size:.8125rem;font-weight:500;cursor:pointer}.attach-add-btn:hover{background:#f8fafc;border-color:#94a3b8}@media(pointer:coarse){.attach-remove:after{content:\"\";position:absolute;inset:-13px}.attach-add-btn{min-height:44px}}\n"]
5809
5948
  }]
5810
5949
  }],
5811
5950
  propDecorators: {
@@ -5825,6 +5964,30 @@ i0.ɵɵngDeclareClassMetadata({
5825
5964
  required: false
5826
5965
  }]
5827
5966
  }],
5967
+ contactEmail: [{
5968
+ type: i0.Input,
5969
+ args: [{
5970
+ isSignal: true,
5971
+ alias: "contactEmail",
5972
+ required: false
5973
+ }]
5974
+ }],
5975
+ allowAttachments: [{
5976
+ type: i0.Input,
5977
+ args: [{
5978
+ isSignal: true,
5979
+ alias: "allowAttachments",
5980
+ required: false
5981
+ }]
5982
+ }],
5983
+ richText: [{
5984
+ type: i0.Input,
5985
+ args: [{
5986
+ isSignal: true,
5987
+ alias: "richText",
5988
+ required: false
5989
+ }]
5990
+ }],
5828
5991
  defaultSubject: [{
5829
5992
  type: i0.Input,
5830
5993
  args: [{
@@ -7507,6 +7670,8 @@ var SendMessagePanelComponent = class SendMessagePanelComponent {
7507
7670
  this.recipientMode = input("contact", ...ngDevMode ? [{ debugName: "recipientMode" }] : /* istanbul ignore next */ []);
7508
7671
  this.sendMessage = input(null, ...ngDevMode ? [{ debugName: "sendMessage" }] : /* istanbul ignore next */ []);
7509
7672
  this.maxBodyLength = input(null, ...ngDevMode ? [{ debugName: "maxBodyLength" }] : /* istanbul ignore next */ []);
7673
+ this.inAppAttachments = input(false, ...ngDevMode ? [{ debugName: "inAppAttachments" }] : /* istanbul ignore next */ []);
7674
+ this.inAppRichText = input(false, ...ngDevMode ? [{ debugName: "inAppRichText" }] : /* istanbul ignore next */ []);
7510
7675
  this.showSubject = input(true, ...ngDevMode ? [{ debugName: "showSubject" }] : /* istanbul ignore next */ []);
7511
7676
  this.showTemplates = input(true, ...ngDevMode ? [{ debugName: "showTemplates" }] : /* istanbul ignore next */ []);
7512
7677
  this.showVideo = input(true, ...ngDevMode ? [{ debugName: "showVideo" }] : /* istanbul ignore next */ []);
@@ -7808,6 +7973,20 @@ var SendMessagePanelComponent = class SendMessagePanelComponent {
7808
7973
  isRequired: false,
7809
7974
  transformFunction: null
7810
7975
  },
7976
+ inAppAttachments: {
7977
+ classPropertyName: "inAppAttachments",
7978
+ publicName: "inAppAttachments",
7979
+ isSignal: true,
7980
+ isRequired: false,
7981
+ transformFunction: null
7982
+ },
7983
+ inAppRichText: {
7984
+ classPropertyName: "inAppRichText",
7985
+ publicName: "inAppRichText",
7986
+ isSignal: true,
7987
+ isRequired: false,
7988
+ transformFunction: null
7989
+ },
7811
7990
  showSubject: {
7812
7991
  classPropertyName: "showSubject",
7813
7992
  publicName: "showSubject",
@@ -8077,6 +8256,9 @@ var SendMessagePanelComponent = class SendMessagePanelComponent {
8077
8256
  #sendInAppRef
8078
8257
  [contactName]="activeContact()!.name"
8079
8258
  [contactId]="activeContact()!.id"
8259
+ [contactEmail]="activeContact()!.email ?? ''"
8260
+ [allowAttachments]="inAppAttachments()"
8261
+ [richText]="inAppRichText()"
8080
8262
  [defaultSubject]="defaultSubject()"
8081
8263
  [defaultBody]="defaultBody()"
8082
8264
  [showSubject]="showSubject()"
@@ -8244,6 +8426,9 @@ var SendMessagePanelComponent = class SendMessagePanelComponent {
8244
8426
  inputs: [
8245
8427
  "contactName",
8246
8428
  "contactId",
8429
+ "contactEmail",
8430
+ "allowAttachments",
8431
+ "richText",
8247
8432
  "defaultSubject",
8248
8433
  "defaultBody",
8249
8434
  "showSubject",
@@ -8439,6 +8624,9 @@ i0.ɵɵngDeclareClassMetadata({
8439
8624
  #sendInAppRef
8440
8625
  [contactName]="activeContact()!.name"
8441
8626
  [contactId]="activeContact()!.id"
8627
+ [contactEmail]="activeContact()!.email ?? ''"
8628
+ [allowAttachments]="inAppAttachments()"
8629
+ [richText]="inAppRichText()"
8442
8630
  [defaultSubject]="defaultSubject()"
8443
8631
  [defaultBody]="defaultBody()"
8444
8632
  [showSubject]="showSubject()"
@@ -8684,6 +8872,22 @@ i0.ɵɵngDeclareClassMetadata({
8684
8872
  required: false
8685
8873
  }]
8686
8874
  }],
8875
+ inAppAttachments: [{
8876
+ type: i0.Input,
8877
+ args: [{
8878
+ isSignal: true,
8879
+ alias: "inAppAttachments",
8880
+ required: false
8881
+ }]
8882
+ }],
8883
+ inAppRichText: [{
8884
+ type: i0.Input,
8885
+ args: [{
8886
+ isSignal: true,
8887
+ alias: "inAppRichText",
8888
+ required: false
8889
+ }]
8890
+ }],
8687
8891
  showSubject: [{
8688
8892
  type: i0.Input,
8689
8893
  args: [{