@forgeroute/ui 0.4.69 → 0.4.71

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.
@@ -869,7 +869,7 @@ var QuickFilterTabsComponent = class QuickFilterTabsComponent {
869
869
  </div>
870
870
  `,
871
871
  isInline: true,
872
- styles: [":host{display:block;min-width:0}.tabs-outer{position:relative;display:inline-flex;align-items:center;max-width:100%;min-width:0}.tabs{display:flex;align-items:center;gap:2px;background:#f1f5f9;border-radius:8px;padding:3px;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch;min-width:0;max-width:100%}.tabs::-webkit-scrollbar{display:none}.tab{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:6px;border:none;background:transparent;font-size:.8125rem;font-weight:500;color:#475569;cursor:pointer;font-family:inherit;transition:background .1s,color .1s;white-space:nowrap;flex-shrink:0}.tab:hover{color:#1e293b}.tab.active{background:#fff;color:#1e293b;box-shadow:0 1px 3px #00000014}.tab:focus-visible{outline:2px solid #60a5fa;outline-offset:2px}.tab.tab-disabled{opacity:.45;cursor:default;pointer-events:none}.tab-count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;border-radius:999px;font-size:.6875rem;font-weight:700;background:#e2e8f0;color:#475569;line-height:1}.active .tab-count,.active-count{background:#1e293b;color:#fff}.more-btn{gap:4px}.more-dropdown{position:absolute;top:calc(100% + 4px);right:0;z-index:100;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 16px #0000001a;min-width:180px;padding:4px;max-height:260px;overflow-y:auto}.more-option{display:flex;align-items:center;justify-content:space-between;width:100%;padding:8px 12px;background:none;border:none;cursor:pointer;font-size:.875rem;color:#334155;font-family:inherit;border-radius:5px;transition:background .1s;text-align:left;gap:12px}.more-option:hover{background:#f8fafc}.more-option.more-option-active{background:#f1f5f9;color:#1e293b;font-weight:500}.more-option:focus-visible{outline:2px solid #60a5fa;outline-offset:-2px}.more-option-label{flex:1}.more-option-count{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:999px;font-size:.6875rem;font-weight:700;line-height:1;background:#e2e8f0;color:#475569}.more-option-active .more-option-count{background:#1e293b;color:#fff}@media(max-width:1024px){.tab{padding:7px 12px}}@media(pointer:coarse){.tab{min-height:44px}}@media(max-width:640px){:host{align-self:flex-start;max-width:100%}.tab{padding:10px 12px;font-size:.8125rem;min-height:44px}.more-option{padding:12px 14px}}\n"],
872
+ styles: [":host{display:block;min-width:0}.tabs-outer{position:relative;display:inline-flex;align-items:center;max-width:100%;min-width:0}.tabs{display:flex;flex-wrap:wrap;align-items:center;gap:2px;background:#f1f5f9;border-radius:8px;padding:3px;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch;min-width:0;max-width:100%}.tabs::-webkit-scrollbar{display:none}.tab{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:6px;border:none;background:transparent;font-size:.8125rem;font-weight:500;color:#475569;cursor:pointer;font-family:inherit;transition:background .1s,color .1s;white-space:nowrap;flex-shrink:0}.tab:hover{color:#1e293b}.tab.active{background:#fff;color:#1e293b;box-shadow:0 1px 3px #00000014}.tab:focus-visible{outline:2px solid #60a5fa;outline-offset:2px}.tab.tab-disabled{opacity:.45;cursor:default;pointer-events:none}.tab-count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;border-radius:999px;font-size:.6875rem;font-weight:700;background:#e2e8f0;color:#475569;line-height:1}.active .tab-count,.active-count{background:#1e293b;color:#fff}.more-btn{gap:4px}.more-dropdown{position:absolute;top:calc(100% + 4px);right:0;z-index:100;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 16px #0000001a;min-width:180px;padding:4px;max-height:260px;overflow-y:auto}.more-option{display:flex;align-items:center;justify-content:space-between;width:100%;padding:8px 12px;background:none;border:none;cursor:pointer;font-size:.875rem;color:#334155;font-family:inherit;border-radius:5px;transition:background .1s;text-align:left;gap:12px}.more-option:hover{background:#f8fafc}.more-option.more-option-active{background:#f1f5f9;color:#1e293b;font-weight:500}.more-option:focus-visible{outline:2px solid #60a5fa;outline-offset:-2px}.more-option-label{flex:1}.more-option-count{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:999px;font-size:.6875rem;font-weight:700;line-height:1;background:#e2e8f0;color:#475569}.more-option-active .more-option-count{background:#1e293b;color:#fff}@media(max-width:1024px){.tab{padding:7px 12px}}@media(pointer:coarse){.tab{min-height:44px}}@media(max-width:640px){:host{align-self:flex-start;max-width:100%}.tab{padding:10px 12px;font-size:.8125rem;min-height:44px}.more-option{padding:12px 14px}}\n"],
873
873
  dependencies: [{
874
874
  kind: "pipe",
875
875
  type: DecimalPipe,
@@ -964,7 +964,7 @@ i0.ɵɵngDeclareClassMetadata({
964
964
  }
965
965
  </div>
966
966
  `,
967
- styles: [":host{display:block;min-width:0}.tabs-outer{position:relative;display:inline-flex;align-items:center;max-width:100%;min-width:0}.tabs{display:flex;align-items:center;gap:2px;background:#f1f5f9;border-radius:8px;padding:3px;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch;min-width:0;max-width:100%}.tabs::-webkit-scrollbar{display:none}.tab{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:6px;border:none;background:transparent;font-size:.8125rem;font-weight:500;color:#475569;cursor:pointer;font-family:inherit;transition:background .1s,color .1s;white-space:nowrap;flex-shrink:0}.tab:hover{color:#1e293b}.tab.active{background:#fff;color:#1e293b;box-shadow:0 1px 3px #00000014}.tab:focus-visible{outline:2px solid #60a5fa;outline-offset:2px}.tab.tab-disabled{opacity:.45;cursor:default;pointer-events:none}.tab-count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;border-radius:999px;font-size:.6875rem;font-weight:700;background:#e2e8f0;color:#475569;line-height:1}.active .tab-count,.active-count{background:#1e293b;color:#fff}.more-btn{gap:4px}.more-dropdown{position:absolute;top:calc(100% + 4px);right:0;z-index:100;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 16px #0000001a;min-width:180px;padding:4px;max-height:260px;overflow-y:auto}.more-option{display:flex;align-items:center;justify-content:space-between;width:100%;padding:8px 12px;background:none;border:none;cursor:pointer;font-size:.875rem;color:#334155;font-family:inherit;border-radius:5px;transition:background .1s;text-align:left;gap:12px}.more-option:hover{background:#f8fafc}.more-option.more-option-active{background:#f1f5f9;color:#1e293b;font-weight:500}.more-option:focus-visible{outline:2px solid #60a5fa;outline-offset:-2px}.more-option-label{flex:1}.more-option-count{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:999px;font-size:.6875rem;font-weight:700;line-height:1;background:#e2e8f0;color:#475569}.more-option-active .more-option-count{background:#1e293b;color:#fff}@media(max-width:1024px){.tab{padding:7px 12px}}@media(pointer:coarse){.tab{min-height:44px}}@media(max-width:640px){:host{align-self:flex-start;max-width:100%}.tab{padding:10px 12px;font-size:.8125rem;min-height:44px}.more-option{padding:12px 14px}}\n"]
967
+ styles: [":host{display:block;min-width:0}.tabs-outer{position:relative;display:inline-flex;align-items:center;max-width:100%;min-width:0}.tabs{display:flex;flex-wrap:wrap;align-items:center;gap:2px;background:#f1f5f9;border-radius:8px;padding:3px;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch;min-width:0;max-width:100%}.tabs::-webkit-scrollbar{display:none}.tab{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:6px;border:none;background:transparent;font-size:.8125rem;font-weight:500;color:#475569;cursor:pointer;font-family:inherit;transition:background .1s,color .1s;white-space:nowrap;flex-shrink:0}.tab:hover{color:#1e293b}.tab.active{background:#fff;color:#1e293b;box-shadow:0 1px 3px #00000014}.tab:focus-visible{outline:2px solid #60a5fa;outline-offset:2px}.tab.tab-disabled{opacity:.45;cursor:default;pointer-events:none}.tab-count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;border-radius:999px;font-size:.6875rem;font-weight:700;background:#e2e8f0;color:#475569;line-height:1}.active .tab-count,.active-count{background:#1e293b;color:#fff}.more-btn{gap:4px}.more-dropdown{position:absolute;top:calc(100% + 4px);right:0;z-index:100;background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 16px #0000001a;min-width:180px;padding:4px;max-height:260px;overflow-y:auto}.more-option{display:flex;align-items:center;justify-content:space-between;width:100%;padding:8px 12px;background:none;border:none;cursor:pointer;font-size:.875rem;color:#334155;font-family:inherit;border-radius:5px;transition:background .1s;text-align:left;gap:12px}.more-option:hover{background:#f8fafc}.more-option.more-option-active{background:#f1f5f9;color:#1e293b;font-weight:500}.more-option:focus-visible{outline:2px solid #60a5fa;outline-offset:-2px}.more-option-label{flex:1}.more-option-count{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border-radius:999px;font-size:.6875rem;font-weight:700;line-height:1;background:#e2e8f0;color:#475569}.more-option-active .more-option-count{background:#1e293b;color:#fff}@media(max-width:1024px){.tab{padding:7px 12px}}@media(pointer:coarse){.tab{min-height:44px}}@media(max-width:640px){:host{align-self:flex-start;max-width:100%}.tab{padding:10px 12px;font-size:.8125rem;min-height:44px}.more-option{padding:12px 14px}}\n"]
968
968
  }]
969
969
  }],
970
970
  ctorParameters: () => [],
@@ -2524,6 +2524,7 @@ var ModalComponent = class ModalComponent {
2524
2524
  this.open = input(false, ...ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []);
2525
2525
  this.title = input("", ...ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []);
2526
2526
  this.description = input("", ...ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []);
2527
+ this.describedBy = input(null, ...ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []);
2527
2528
  this.maxWidth = input("460px", ...ngDevMode ? [{ debugName: "maxWidth" }] : /* istanbul ignore next */ []);
2528
2529
  this.bodyOverflow = input("auto", ...ngDevMode ? [{ debugName: "bodyOverflow" }] : /* istanbul ignore next */ []);
2529
2530
  this.dialogTestId = input("", ...ngDevMode ? [{ debugName: "dialogTestId" }] : /* istanbul ignore next */ []);
@@ -2537,6 +2538,8 @@ var ModalComponent = class ModalComponent {
2537
2538
  this.returnFocusTo = input(null, ...ngDevMode ? [{ debugName: "returnFocusTo" }] : /* istanbul ignore next */ []);
2538
2539
  this.kiosk = computed(() => this.layout() === "kiosk", ...ngDevMode ? [{ debugName: "kiosk" }] : /* istanbul ignore next */ []);
2539
2540
  this.titleId = `m-title-${Math.random().toString(36).slice(2, 8)}`;
2541
+ this.descId = `m-desc-${Math.random().toString(36).slice(2, 8)}`;
2542
+ this.describedById = computed(() => this.describedBy()?.trim() || (this.description() ? this.descId : null), ...ngDevMode ? [{ debugName: "describedById" }] : /* istanbul ignore next */ []);
2540
2543
  this.el = inject(ElementRef);
2541
2544
  this.ren = inject(Renderer2);
2542
2545
  this.doc = inject(DOCUMENT);
@@ -2674,6 +2677,13 @@ var ModalComponent = class ModalComponent {
2674
2677
  isRequired: false,
2675
2678
  transformFunction: null
2676
2679
  },
2680
+ describedBy: {
2681
+ classPropertyName: "describedBy",
2682
+ publicName: "describedBy",
2683
+ isSignal: true,
2684
+ isRequired: false,
2685
+ transformFunction: null
2686
+ },
2677
2687
  maxWidth: {
2678
2688
  classPropertyName: "maxWidth",
2679
2689
  publicName: "maxWidth",
@@ -2751,13 +2761,13 @@ var ModalComponent = class ModalComponent {
2751
2761
  template: `
2752
2762
  @if (open()) {
2753
2763
  <div class="m-backdrop" [class.m-kiosk-backdrop]="kiosk()" (click)="dismiss('backdrop')"></div>
2754
- <div class="m-modal" [class.m-kiosk]="kiosk()" role="dialog" aria-modal="true" tabindex="-1" [attr.aria-labelledby]="titleId" [attr.aria-busy]="busy() ? 'true' : null" [attr.data-testid]="dialogTestId() || null" [style.max-width]="maxWidth()">
2764
+ <div class="m-modal" [class.m-kiosk]="kiosk()" role="dialog" aria-modal="true" tabindex="-1" [attr.aria-labelledby]="titleId" [attr.aria-describedby]="describedById()" [attr.aria-busy]="busy() ? 'true' : null" [attr.data-testid]="dialogTestId() || null" [style.max-width]="maxWidth()">
2755
2765
  <span class="m-focus-guard" data-ui-focus-guard="start" tabindex="0" (focus)="onGuardFocus('start', $event)"></span>
2756
2766
  <div class="m-header">
2757
2767
  <div class="m-header-text">
2758
2768
  <h3 class="m-title" [id]="titleId">{{ title() }}</h3>
2759
2769
  @if (description()) {
2760
- <p class="m-description">{{ description() }}</p>
2770
+ <p class="m-description" [id]="descId">{{ description() }}</p>
2761
2771
  }
2762
2772
  </div>
2763
2773
  <ng-content select="[modal-header-end]" />
@@ -2802,13 +2812,13 @@ i0.ɵɵngDeclareClassMetadata({
2802
2812
  template: `
2803
2813
  @if (open()) {
2804
2814
  <div class="m-backdrop" [class.m-kiosk-backdrop]="kiosk()" (click)="dismiss('backdrop')"></div>
2805
- <div class="m-modal" [class.m-kiosk]="kiosk()" role="dialog" aria-modal="true" tabindex="-1" [attr.aria-labelledby]="titleId" [attr.aria-busy]="busy() ? 'true' : null" [attr.data-testid]="dialogTestId() || null" [style.max-width]="maxWidth()">
2815
+ <div class="m-modal" [class.m-kiosk]="kiosk()" role="dialog" aria-modal="true" tabindex="-1" [attr.aria-labelledby]="titleId" [attr.aria-describedby]="describedById()" [attr.aria-busy]="busy() ? 'true' : null" [attr.data-testid]="dialogTestId() || null" [style.max-width]="maxWidth()">
2806
2816
  <span class="m-focus-guard" data-ui-focus-guard="start" tabindex="0" (focus)="onGuardFocus('start', $event)"></span>
2807
2817
  <div class="m-header">
2808
2818
  <div class="m-header-text">
2809
2819
  <h3 class="m-title" [id]="titleId">{{ title() }}</h3>
2810
2820
  @if (description()) {
2811
- <p class="m-description">{{ description() }}</p>
2821
+ <p class="m-description" [id]="descId">{{ description() }}</p>
2812
2822
  }
2813
2823
  </div>
2814
2824
  <ng-content select="[modal-header-end]" />
@@ -2861,6 +2871,14 @@ i0.ɵɵngDeclareClassMetadata({
2861
2871
  required: false
2862
2872
  }]
2863
2873
  }],
2874
+ describedBy: [{
2875
+ type: i0.Input,
2876
+ args: [{
2877
+ isSignal: true,
2878
+ alias: "describedBy",
2879
+ required: false
2880
+ }]
2881
+ }],
2864
2882
  maxWidth: [{
2865
2883
  type: i0.Input,
2866
2884
  args: [{
@@ -6276,6 +6294,7 @@ var TableToolbarComponent = class TableToolbarComponent {
6276
6294
  "open",
6277
6295
  "title",
6278
6296
  "description",
6297
+ "describedBy",
6279
6298
  "maxWidth",
6280
6299
  "bodyOverflow",
6281
6300
  "dialogTestId",
@@ -7886,66 +7905,154 @@ i0.ɵɵngDeclareClassMetadata({
7886
7905
  }
7887
7906
  });
7888
7907
  const MIN_RESUME_MS = 1e3;
7908
+ const QUEUE_GAP_MS = 80;
7909
+ const COUNTDOWN_TICK_MS = 200;
7910
+ const DEFAULT_MAX_QUEUED = 20;
7889
7911
  var ToastService = class ToastService {
7890
7912
  constructor() {
7891
7913
  this.message = signal(null, ...ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []);
7892
7914
  this.type = signal("success", ...ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []);
7893
7915
  this.dismissible = signal(false, ...ngDevMode ? [{ debugName: "dismissible" }] : /* istanbul ignore next */ []);
7894
7916
  this.persistentLiveRegion = signal(false, ...ngDevMode ? [{ debugName: "persistentLiveRegion" }] : /* istanbul ignore next */ []);
7917
+ this.details = signal(null, ...ngDevMode ? [{ debugName: "details" }] : /* istanbul ignore next */ []);
7918
+ this.queuedCount = signal(0, ...ngDevMode ? [{ debugName: "queuedCount" }] : /* istanbul ignore next */ []);
7919
+ this.queueDefault = signal(false, ...ngDevMode ? [{ debugName: "queueDefault" }] : /* istanbul ignore next */ []);
7920
+ this.countdownDefault = signal(false, ...ngDevMode ? [{ debugName: "countdownDefault" }] : /* istanbul ignore next */ []);
7921
+ this.maxQueued = signal(DEFAULT_MAX_QUEUED, ...ngDevMode ? [{ debugName: "maxQueued" }] : /* istanbul ignore next */ []);
7922
+ this.remainingMs = signal(0, ...ngDevMode ? [{ debugName: "remainingMs" }] : /* istanbul ignore next */ []);
7923
+ this.totalMs = signal(0, ...ngDevMode ? [{ debugName: "totalMs" }] : /* istanbul ignore next */ []);
7924
+ this.action$ = new Subject();
7895
7925
  this._timer = null;
7926
+ this._ticker = null;
7927
+ this._advanceTimer = null;
7896
7928
  this._deadline = 0;
7897
7929
  this._remaining = 0;
7898
7930
  this._paused = false;
7931
+ this._queue = [];
7899
7932
  }
7900
- show(msg, duration = 3e3) {
7901
- this._display("success", msg, duration);
7933
+ show(msg, durationOrOptions = 3e3) {
7934
+ this._enter("success", msg, 3e3, durationOrOptions);
7902
7935
  }
7903
- success(msg, duration = 3e3) {
7904
- this._display("success", msg, duration);
7936
+ success(msg, durationOrOptions = 3e3) {
7937
+ this._enter("success", msg, 3e3, durationOrOptions);
7905
7938
  }
7906
- info(msg, duration = 3e3) {
7907
- this._display("info", msg, duration);
7939
+ info(msg, durationOrOptions = 3e3) {
7940
+ this._enter("info", msg, 3e3, durationOrOptions);
7908
7941
  }
7909
- error(msg, duration = 4e3) {
7910
- this._display("error", msg, duration);
7942
+ error(msg, durationOrOptions = 4e3) {
7943
+ this._enter("error", msg, 4e3, durationOrOptions);
7944
+ }
7945
+ notify(msg, options = {}) {
7946
+ const type = options.type ?? "success";
7947
+ this._enter(type, msg, type === "error" ? 4e3 : 3e3, options);
7911
7948
  }
7912
7949
  dismiss() {
7913
- this._clearTimer();
7914
- this._paused = false;
7915
- this.message.set(null);
7950
+ this._finish();
7951
+ }
7952
+ runAction(id) {
7953
+ const d = this.details();
7954
+ const a = d?.actions.find((x) => x.id === id);
7955
+ if (!d || !a || a.disabled) return;
7956
+ d.onAction?.(id);
7957
+ this.action$.next({
7958
+ id,
7959
+ key: d.key
7960
+ });
7961
+ if (!a.keepOpen) this._finish();
7916
7962
  }
7917
7963
  pause() {
7918
7964
  if (this._paused || !this._timer || this.message() === null) return;
7919
7965
  this._remaining = Math.max(0, this._deadline - Date.now());
7966
+ this.remainingMs.set(this._remaining);
7920
7967
  this._clearTimer();
7921
7968
  this._paused = true;
7922
7969
  }
7923
7970
  resume() {
7924
7971
  if (!this._paused) return;
7925
7972
  this._paused = false;
7926
- this._start(Math.max(this._remaining, MIN_RESUME_MS));
7973
+ this._start(Math.max(this._remaining, MIN_RESUME_MS), false);
7927
7974
  }
7928
7975
  ngOnDestroy() {
7929
7976
  this._clearTimer();
7977
+ this._clearTicker();
7978
+ this._clearAdvance();
7979
+ this._queue = [];
7930
7980
  this._paused = false;
7981
+ this.action$.complete();
7931
7982
  }
7932
7983
  _clearTimer() {
7933
7984
  if (this._timer) clearTimeout(this._timer);
7934
7985
  this._timer = null;
7935
7986
  }
7936
- _start(duration) {
7987
+ _clearTicker() {
7988
+ if (this._ticker) clearInterval(this._ticker);
7989
+ this._ticker = null;
7990
+ }
7991
+ _clearAdvance() {
7992
+ if (this._advanceTimer) clearTimeout(this._advanceTimer);
7993
+ this._advanceTimer = null;
7994
+ }
7995
+ _start(duration, fresh) {
7937
7996
  this._deadline = Date.now() + duration;
7938
- this._timer = setTimeout(() => {
7939
- this.message.set(null);
7940
- this._timer = null;
7941
- }, duration);
7997
+ this._timer = setTimeout(() => this._finish(), duration);
7998
+ if (fresh) this.totalMs.set(duration);
7999
+ this.remainingMs.set(duration);
8000
+ if (this.details()?.countdown && !this._ticker) this._ticker = setInterval(() => {
8001
+ if (!this._paused && this._timer) this.remainingMs.set(Math.max(0, this._deadline - Date.now()));
8002
+ }, COUNTDOWN_TICK_MS);
8003
+ }
8004
+ _finish() {
8005
+ this._clearTimer();
8006
+ this._clearTicker();
8007
+ this._paused = false;
8008
+ this.message.set(null);
8009
+ this.details.set(null);
8010
+ this.remainingMs.set(0);
8011
+ if (this._queue.length > 0 && !this._advanceTimer) this._advanceTimer = setTimeout(() => {
8012
+ this._advanceTimer = null;
8013
+ const next = this._queue.shift();
8014
+ this.queuedCount.set(this._queue.length);
8015
+ if (next) this._present(next);
8016
+ }, QUEUE_GAP_MS);
8017
+ }
8018
+ _enter(type, msg, defaultDuration, arg) {
8019
+ const options = typeof arg === "number" ? { duration: arg } : arg ?? {};
8020
+ const hasRich = !!(options.title || options.actions?.length);
8021
+ const countdown = options.countdown ?? this.countdownDefault();
8022
+ const details = hasRich || countdown || options.key !== void 0 || !!options.onAction ? {
8023
+ title: options.title,
8024
+ actions: options.actions ?? [],
8025
+ countdown,
8026
+ key: options.key,
8027
+ onAction: options.onAction
8028
+ } : null;
8029
+ const pending = {
8030
+ type,
8031
+ message: msg,
8032
+ duration: options.duration ?? defaultDuration,
8033
+ details
8034
+ };
8035
+ const queue = options.queue ?? this.queueDefault();
8036
+ const busy = this.message() !== null || this._advanceTimer !== null;
8037
+ if (queue && busy) {
8038
+ const key = options.key;
8039
+ if (key !== void 0 && (this.details()?.key === key || this._queue.some((q) => q.details?.key === key))) return;
8040
+ if (this._queue.length >= this.maxQueued()) return;
8041
+ this._queue.push(pending);
8042
+ this.queuedCount.set(this._queue.length);
8043
+ return;
8044
+ }
8045
+ this._clearAdvance();
8046
+ this._present(pending);
7942
8047
  }
7943
- _display(type, msg, duration) {
8048
+ _present(p) {
7944
8049
  this._clearTimer();
8050
+ this._clearTicker();
7945
8051
  this._paused = false;
7946
- this.type.set(type);
7947
- this.message.set(msg);
7948
- this._start(duration);
8052
+ this.type.set(p.type);
8053
+ this.details.set(p.details);
8054
+ this.message.set(p.message);
8055
+ this._start(p.duration, true);
7949
8056
  }
7950
8057
  static {
7951
8058
  this.ɵfac = i0.ɵɵngDeclareFactory({
@@ -8019,6 +8126,9 @@ i0.ɵɵngDeclareClassMetadata({
8019
8126
  }]
8020
8127
  });
8021
8128
  var ToastComponent = class ToastComponent {
8129
+ onAction(id) {
8130
+ this.toast.runAction(id);
8131
+ }
8022
8132
  onEscape(e) {
8023
8133
  e.stopPropagation();
8024
8134
  this.toast.dismiss();
@@ -8049,6 +8159,22 @@ var ToastComponent = class ToastComponent {
8049
8159
  this.showClose = computed(() => this.dismissible() ?? this.toast.dismissible(), ...ngDevMode ? [{ debugName: "showClose" }] : /* istanbul ignore next */ []);
8050
8160
  this.persistentLiveRegion = input(null, ...ngDevMode ? [{ debugName: "persistentLiveRegion" }] : /* istanbul ignore next */ []);
8051
8161
  this.persistent = computed(() => this.persistentLiveRegion() ?? this.toast.persistentLiveRegion(), ...ngDevMode ? [{ debugName: "persistent" }] : /* istanbul ignore next */ []);
8162
+ this.queue = input(null, ...ngDevMode ? [{ debugName: "queue" }] : /* istanbul ignore next */ []);
8163
+ this.countdown = input(null, ...ngDevMode ? [{ debugName: "countdown" }] : /* istanbul ignore next */ []);
8164
+ this.maxQueued = input(null, ...ngDevMode ? [{ debugName: "maxQueued" }] : /* istanbul ignore next */ []);
8165
+ this.countdownLabel = input("Dismissing in {n}s", ...ngDevMode ? [{ debugName: "countdownLabel" }] : /* istanbul ignore next */ []);
8166
+ this.queueLabel = input("+{n} more", ...ngDevMode ? [{ debugName: "queueLabel" }] : /* istanbul ignore next */ []);
8167
+ this.action = output();
8168
+ this.isRich = computed(() => {
8169
+ const d = this.toast.details();
8170
+ return !!d && (!!d.title || d.actions.length > 0 || d.countdown);
8171
+ }, ...ngDevMode ? [{ debugName: "isRich" }] : /* istanbul ignore next */ []);
8172
+ this.countdownPct = computed(() => {
8173
+ const total = this.toast.totalMs();
8174
+ return total > 0 ? Math.min(100, Math.max(0, this.toast.remainingMs() / total * 100)) : 0;
8175
+ }, ...ngDevMode ? [{ debugName: "countdownPct" }] : /* istanbul ignore next */ []);
8176
+ this.countdownText = computed(() => this.countdownLabel().replace("{n}", String(Math.ceil(this.toast.remainingMs() / 1e3))), ...ngDevMode ? [{ debugName: "countdownText" }] : /* istanbul ignore next */ []);
8177
+ this.queueText = computed(() => this.queueLabel().replace("{n}", String(this.toast.queuedCount())), ...ngDevMode ? [{ debugName: "queueText" }] : /* istanbul ignore next */ []);
8052
8178
  this._adoptedMessage = signal(null, ...ngDevMode ? [{ debugName: "_adoptedMessage" }] : /* istanbul ignore next */ []);
8053
8179
  this.adopted = computed(() => {
8054
8180
  const m = this._adoptedMessage();
@@ -8059,6 +8185,18 @@ var ToastComponent = class ToastComponent {
8059
8185
  this._focus = false;
8060
8186
  this._registry.register(this._id);
8061
8187
  inject(DestroyRef).onDestroy(() => this._registry.unregister(this._id));
8188
+ effect(() => {
8189
+ if (!this.isHost()) return;
8190
+ const q = this.queue(), c = this.countdown(), m = this.maxQueued();
8191
+ untracked(() => {
8192
+ if (q !== null) this.toast.queueDefault.set(q);
8193
+ if (c !== null) this.toast.countdownDefault.set(c);
8194
+ if (m !== null) this.toast.maxQueued.set(Math.max(0, Math.floor(m)));
8195
+ });
8196
+ });
8197
+ this.toast.action$.pipe(takeUntilDestroyed()).subscribe((e) => {
8198
+ if (this.isHost()) this.action.emit(e);
8199
+ });
8062
8200
  effect(() => {
8063
8201
  const host = this.isHost();
8064
8202
  const msg = this.toast.message();
@@ -8109,8 +8247,44 @@ var ToastComponent = class ToastComponent {
8109
8247
  isSignal: true,
8110
8248
  isRequired: false,
8111
8249
  transformFunction: null
8250
+ },
8251
+ queue: {
8252
+ classPropertyName: "queue",
8253
+ publicName: "queue",
8254
+ isSignal: true,
8255
+ isRequired: false,
8256
+ transformFunction: null
8257
+ },
8258
+ countdown: {
8259
+ classPropertyName: "countdown",
8260
+ publicName: "countdown",
8261
+ isSignal: true,
8262
+ isRequired: false,
8263
+ transformFunction: null
8264
+ },
8265
+ maxQueued: {
8266
+ classPropertyName: "maxQueued",
8267
+ publicName: "maxQueued",
8268
+ isSignal: true,
8269
+ isRequired: false,
8270
+ transformFunction: null
8271
+ },
8272
+ countdownLabel: {
8273
+ classPropertyName: "countdownLabel",
8274
+ publicName: "countdownLabel",
8275
+ isSignal: true,
8276
+ isRequired: false,
8277
+ transformFunction: null
8278
+ },
8279
+ queueLabel: {
8280
+ classPropertyName: "queueLabel",
8281
+ publicName: "queueLabel",
8282
+ isSignal: true,
8283
+ isRequired: false,
8284
+ transformFunction: null
8112
8285
  }
8113
8286
  },
8287
+ outputs: { action: "action" },
8114
8288
  ngImport: i0,
8115
8289
  template: `
8116
8290
  @if (isHost() && persistent()) {
@@ -8128,6 +8302,7 @@ var ToastComponent = class ToastComponent {
8128
8302
  <ng-template #card>
8129
8303
  <div
8130
8304
  class="t-toast"
8305
+ [class.t-rich]="isRich()"
8131
8306
  [class.t-error]="toast.type() === 'error'"
8132
8307
  [class.t-info]="toast.type() === 'info'"
8133
8308
  [class.t-adopted]="adopted()"
@@ -8149,7 +8324,14 @@ var ToastComponent = class ToastComponent {
8149
8324
  <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
8150
8325
  </svg>
8151
8326
  }
8152
- <span class="t-text">{{ toast.message() }}</span>
8327
+ @if (isRich()) {
8328
+ <span class="t-content">
8329
+ @if (toast.details()?.title; as title) { <span class="t-title" data-testid="toast-title">{{ title }}</span> }
8330
+ <span class="t-text">{{ toast.message() }}</span>
8331
+ </span>
8332
+ } @else {
8333
+ <span class="t-text">{{ toast.message() }}</span>
8334
+ }
8153
8335
  @if (showClose()) {
8154
8336
  <button type="button" class="t-close" aria-label="Dismiss notification" (click)="toast.dismiss()">
8155
8337
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" aria-hidden="true">
@@ -8157,11 +8339,31 @@ var ToastComponent = class ToastComponent {
8157
8339
  </svg>
8158
8340
  </button>
8159
8341
  }
8342
+ @if (toast.queuedCount() > 0) {
8343
+ <span class="t-queue" data-testid="toast-queue">{{ queueText() }}</span>
8344
+ }
8345
+ @if (toast.details(); as d) {
8346
+ @if (d.actions.length > 0) {
8347
+ <div class="t-actions" role="group" aria-label="Actions" data-testid="toast-actions">
8348
+ @for (a of d.actions; track a.id) {
8349
+ <button type="button" class="t-action"
8350
+ [class.t-action-primary]="a.variant === 'primary'" [class.t-action-danger]="a.variant === 'danger'"
8351
+ [disabled]="a.disabled" [attr.data-testid]="'toast-action-' + a.id" (click)="onAction(a.id)">{{ a.label }}</button>
8352
+ }
8353
+ </div>
8354
+ }
8355
+ @if (d.countdown) {
8356
+ <div class="t-countdown" aria-hidden="true">
8357
+ <div class="t-countdown-bar"><div class="t-countdown-fill" [style.width.%]="countdownPct()" data-testid="toast-countdown-fill"></div></div>
8358
+ <div class="t-countdown-label" data-testid="toast-countdown">{{ countdownText() }}</div>
8359
+ </div>
8360
+ }
8361
+ }
8160
8362
  </div>
8161
8363
  </ng-template>
8162
8364
  `,
8163
8365
  isInline: true,
8164
- styles: [".t-live{position:absolute;width:0;height:0;margin:0;padding:0;overflow:visible}.t-toast{position:fixed;bottom:max(24px,env(safe-area-inset-bottom,0px));left:50%;transform:translate(-50%);background:#047857;color:#fff;padding:11px 20px;border-radius:12px;font-size:.875rem;font-weight:500;z-index:9999;box-shadow:0 4px 16px #0000002e;animation:t-in .28s cubic-bezier(.16,1,.3,1);display:flex;align-items:center;gap:8px;max-width:calc(100vw - 32px - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px));overflow-wrap:anywhere;word-break:break-word}.t-error{background:#dc2626}.t-info{background:#2563eb}.t-adopted{animation:none}.t-text{min-width:0}.t-close{position:relative;flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;margin:-4px -10px -4px 0;padding:0;border:0;border-radius:50%;background:transparent;color:inherit;cursor:pointer}.t-close:after{content:\"\";position:absolute;inset:-8px}.t-close:hover{background:#fff3}.t-close:focus-visible{outline:2px solid #fff;outline-offset:1px}@keyframes t-in{0%{opacity:0;transform:translate(-50%) translateY(10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}@media(prefers-reduced-motion:reduce){.t-toast{animation:none}}@media(max-width:480px){.t-toast{bottom:max(16px,env(safe-area-inset-bottom,16px));font-size:.8125rem;padding:10px 16px}}\n"],
8366
+ styles: [".t-live{position:absolute;width:0;height:0;margin:0;padding:0;overflow:visible}.t-toast{position:fixed;bottom:max(24px,env(safe-area-inset-bottom,0px));left:50%;transform:translate(-50%);background:#047857;color:#fff;padding:11px 20px;border-radius:12px;font-size:.875rem;font-weight:500;z-index:9999;box-shadow:0 4px 16px #0000002e;animation:t-in .28s cubic-bezier(.16,1,.3,1);display:flex;align-items:center;gap:8px;max-width:calc(100vw - 32px - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px));overflow-wrap:anywhere;word-break:break-word}.t-error{background:#dc2626}.t-info{background:#2563eb}.t-adopted{animation:none}.t-text{min-width:0}.t-close{position:relative;flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;margin:-4px -10px -4px 0;padding:0;border:0;border-radius:50%;background:transparent;color:inherit;cursor:pointer}.t-close:after{content:\"\";position:absolute;inset:-8px}.t-close:hover{background:#fff3}.t-close:focus-visible{outline:2px solid #fff;outline-offset:1px}.t-rich{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:start;column-gap:8px;row-gap:10px;min-width:min(320px,calc(100vw - 32px))}.t-rich>svg:first-child{margin-top:2px}.t-content{display:flex;flex-direction:column;gap:2px;min-width:0}.t-title{font-weight:700}.t-queue{grid-column:1 / -1;justify-self:end;font-size:.75rem;font-weight:600;padding:1px 8px;border-radius:999px;background:#ffffff38}.t-actions{grid-column:1 / -1;display:flex;flex-wrap:wrap;gap:8px}.t-action{min-height:32px;padding:6px 14px;border-radius:8px;font:inherit;font-size:.8125rem;font-weight:600;cursor:pointer;color:#fff;background:#ffffff2e;border:1px solid rgba(255,255,255,.6)}.t-action:hover:not(:disabled){background:#ffffff4d}.t-action:focus-visible{outline:2px solid #fff;outline-offset:2px}.t-action:disabled{opacity:.5;cursor:not-allowed}.t-action-primary{background:#fff;color:#0f172a;border-color:#fff}.t-action-primary:hover:not(:disabled){background:#f1f5f9}.t-action-danger{background:#7f1d1d;border-color:#ffffffb3}.t-action-danger:hover:not(:disabled){background:#991b1b}.t-countdown{grid-column:1 / -1;display:flex;flex-direction:column;gap:4px}.t-countdown-bar{height:4px;border-radius:2px;background:#ffffff47;overflow:hidden}.t-countdown-fill{height:100%;background:#fff;transition:width .2s linear}.t-countdown-label{font-size:.75rem;opacity:.85;font-variant-numeric:tabular-nums}@media(prefers-reduced-motion:reduce){.t-countdown-fill{transition:none}}@media(pointer:coarse),(max-width:480px){.t-action{min-height:44px}}@keyframes t-in{0%{opacity:0;transform:translate(-50%) translateY(10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}@media(prefers-reduced-motion:reduce){.t-toast{animation:none}}@media(max-width:480px){.t-toast{bottom:max(16px,env(safe-area-inset-bottom,16px));font-size:.8125rem;padding:10px 16px}}\n"],
8165
8367
  dependencies: [{
8166
8368
  kind: "directive",
8167
8369
  type: NgTemplateOutlet,
@@ -8204,6 +8406,7 @@ i0.ɵɵngDeclareClassMetadata({
8204
8406
  <ng-template #card>
8205
8407
  <div
8206
8408
  class="t-toast"
8409
+ [class.t-rich]="isRich()"
8207
8410
  [class.t-error]="toast.type() === 'error'"
8208
8411
  [class.t-info]="toast.type() === 'info'"
8209
8412
  [class.t-adopted]="adopted()"
@@ -8225,7 +8428,14 @@ i0.ɵɵngDeclareClassMetadata({
8225
8428
  <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
8226
8429
  </svg>
8227
8430
  }
8228
- <span class="t-text">{{ toast.message() }}</span>
8431
+ @if (isRich()) {
8432
+ <span class="t-content">
8433
+ @if (toast.details()?.title; as title) { <span class="t-title" data-testid="toast-title">{{ title }}</span> }
8434
+ <span class="t-text">{{ toast.message() }}</span>
8435
+ </span>
8436
+ } @else {
8437
+ <span class="t-text">{{ toast.message() }}</span>
8438
+ }
8229
8439
  @if (showClose()) {
8230
8440
  <button type="button" class="t-close" aria-label="Dismiss notification" (click)="toast.dismiss()">
8231
8441
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" aria-hidden="true">
@@ -8233,10 +8443,30 @@ i0.ɵɵngDeclareClassMetadata({
8233
8443
  </svg>
8234
8444
  </button>
8235
8445
  }
8446
+ @if (toast.queuedCount() > 0) {
8447
+ <span class="t-queue" data-testid="toast-queue">{{ queueText() }}</span>
8448
+ }
8449
+ @if (toast.details(); as d) {
8450
+ @if (d.actions.length > 0) {
8451
+ <div class="t-actions" role="group" aria-label="Actions" data-testid="toast-actions">
8452
+ @for (a of d.actions; track a.id) {
8453
+ <button type="button" class="t-action"
8454
+ [class.t-action-primary]="a.variant === 'primary'" [class.t-action-danger]="a.variant === 'danger'"
8455
+ [disabled]="a.disabled" [attr.data-testid]="'toast-action-' + a.id" (click)="onAction(a.id)">{{ a.label }}</button>
8456
+ }
8457
+ </div>
8458
+ }
8459
+ @if (d.countdown) {
8460
+ <div class="t-countdown" aria-hidden="true">
8461
+ <div class="t-countdown-bar"><div class="t-countdown-fill" [style.width.%]="countdownPct()" data-testid="toast-countdown-fill"></div></div>
8462
+ <div class="t-countdown-label" data-testid="toast-countdown">{{ countdownText() }}</div>
8463
+ </div>
8464
+ }
8465
+ }
8236
8466
  </div>
8237
8467
  </ng-template>
8238
8468
  `,
8239
- styles: [".t-live{position:absolute;width:0;height:0;margin:0;padding:0;overflow:visible}.t-toast{position:fixed;bottom:max(24px,env(safe-area-inset-bottom,0px));left:50%;transform:translate(-50%);background:#047857;color:#fff;padding:11px 20px;border-radius:12px;font-size:.875rem;font-weight:500;z-index:9999;box-shadow:0 4px 16px #0000002e;animation:t-in .28s cubic-bezier(.16,1,.3,1);display:flex;align-items:center;gap:8px;max-width:calc(100vw - 32px - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px));overflow-wrap:anywhere;word-break:break-word}.t-error{background:#dc2626}.t-info{background:#2563eb}.t-adopted{animation:none}.t-text{min-width:0}.t-close{position:relative;flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;margin:-4px -10px -4px 0;padding:0;border:0;border-radius:50%;background:transparent;color:inherit;cursor:pointer}.t-close:after{content:\"\";position:absolute;inset:-8px}.t-close:hover{background:#fff3}.t-close:focus-visible{outline:2px solid #fff;outline-offset:1px}@keyframes t-in{0%{opacity:0;transform:translate(-50%) translateY(10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}@media(prefers-reduced-motion:reduce){.t-toast{animation:none}}@media(max-width:480px){.t-toast{bottom:max(16px,env(safe-area-inset-bottom,16px));font-size:.8125rem;padding:10px 16px}}\n"]
8469
+ styles: [".t-live{position:absolute;width:0;height:0;margin:0;padding:0;overflow:visible}.t-toast{position:fixed;bottom:max(24px,env(safe-area-inset-bottom,0px));left:50%;transform:translate(-50%);background:#047857;color:#fff;padding:11px 20px;border-radius:12px;font-size:.875rem;font-weight:500;z-index:9999;box-shadow:0 4px 16px #0000002e;animation:t-in .28s cubic-bezier(.16,1,.3,1);display:flex;align-items:center;gap:8px;max-width:calc(100vw - 32px - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px));overflow-wrap:anywhere;word-break:break-word}.t-error{background:#dc2626}.t-info{background:#2563eb}.t-adopted{animation:none}.t-text{min-width:0}.t-close{position:relative;flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;margin:-4px -10px -4px 0;padding:0;border:0;border-radius:50%;background:transparent;color:inherit;cursor:pointer}.t-close:after{content:\"\";position:absolute;inset:-8px}.t-close:hover{background:#fff3}.t-close:focus-visible{outline:2px solid #fff;outline-offset:1px}.t-rich{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:start;column-gap:8px;row-gap:10px;min-width:min(320px,calc(100vw - 32px))}.t-rich>svg:first-child{margin-top:2px}.t-content{display:flex;flex-direction:column;gap:2px;min-width:0}.t-title{font-weight:700}.t-queue{grid-column:1 / -1;justify-self:end;font-size:.75rem;font-weight:600;padding:1px 8px;border-radius:999px;background:#ffffff38}.t-actions{grid-column:1 / -1;display:flex;flex-wrap:wrap;gap:8px}.t-action{min-height:32px;padding:6px 14px;border-radius:8px;font:inherit;font-size:.8125rem;font-weight:600;cursor:pointer;color:#fff;background:#ffffff2e;border:1px solid rgba(255,255,255,.6)}.t-action:hover:not(:disabled){background:#ffffff4d}.t-action:focus-visible{outline:2px solid #fff;outline-offset:2px}.t-action:disabled{opacity:.5;cursor:not-allowed}.t-action-primary{background:#fff;color:#0f172a;border-color:#fff}.t-action-primary:hover:not(:disabled){background:#f1f5f9}.t-action-danger{background:#7f1d1d;border-color:#ffffffb3}.t-action-danger:hover:not(:disabled){background:#991b1b}.t-countdown{grid-column:1 / -1;display:flex;flex-direction:column;gap:4px}.t-countdown-bar{height:4px;border-radius:2px;background:#ffffff47;overflow:hidden}.t-countdown-fill{height:100%;background:#fff;transition:width .2s linear}.t-countdown-label{font-size:.75rem;opacity:.85;font-variant-numeric:tabular-nums}@media(prefers-reduced-motion:reduce){.t-countdown-fill{transition:none}}@media(pointer:coarse),(max-width:480px){.t-action{min-height:44px}}@keyframes t-in{0%{opacity:0;transform:translate(-50%) translateY(10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}@media(prefers-reduced-motion:reduce){.t-toast{animation:none}}@media(max-width:480px){.t-toast{bottom:max(16px,env(safe-area-inset-bottom,16px));font-size:.8125rem;padding:10px 16px}}\n"]
8240
8470
  }]
8241
8471
  }],
8242
8472
  ctorParameters: () => [],
@@ -8256,6 +8486,50 @@ i0.ɵɵngDeclareClassMetadata({
8256
8486
  alias: "persistentLiveRegion",
8257
8487
  required: false
8258
8488
  }]
8489
+ }],
8490
+ queue: [{
8491
+ type: i0.Input,
8492
+ args: [{
8493
+ isSignal: true,
8494
+ alias: "queue",
8495
+ required: false
8496
+ }]
8497
+ }],
8498
+ countdown: [{
8499
+ type: i0.Input,
8500
+ args: [{
8501
+ isSignal: true,
8502
+ alias: "countdown",
8503
+ required: false
8504
+ }]
8505
+ }],
8506
+ maxQueued: [{
8507
+ type: i0.Input,
8508
+ args: [{
8509
+ isSignal: true,
8510
+ alias: "maxQueued",
8511
+ required: false
8512
+ }]
8513
+ }],
8514
+ countdownLabel: [{
8515
+ type: i0.Input,
8516
+ args: [{
8517
+ isSignal: true,
8518
+ alias: "countdownLabel",
8519
+ required: false
8520
+ }]
8521
+ }],
8522
+ queueLabel: [{
8523
+ type: i0.Input,
8524
+ args: [{
8525
+ isSignal: true,
8526
+ alias: "queueLabel",
8527
+ required: false
8528
+ }]
8529
+ }],
8530
+ action: [{
8531
+ type: i0.Output,
8532
+ args: ["action"]
8259
8533
  }]
8260
8534
  }
8261
8535
  });
@@ -10236,6 +10510,7 @@ var ImportExportComponent = class ImportExportComponent {
10236
10510
  "open",
10237
10511
  "title",
10238
10512
  "description",
10513
+ "describedBy",
10239
10514
  "maxWidth",
10240
10515
  "bodyOverflow",
10241
10516
  "dialogTestId",
@@ -10718,6 +10993,10 @@ var ConfirmOutletComponent = class ConfirmOutletComponent {
10718
10993
  constructor() {
10719
10994
  this.svc = inject(ConfirmService);
10720
10995
  this._isPrimary = computed(() => this.svc.primaryOutlet() === this, ...ngDevMode ? [{ debugName: "_isPrimary" }] : /* istanbul ignore next */ []);
10996
+ this._uid = Math.random().toString(36).slice(2, 8);
10997
+ this.msgId = `ui-confirm-msg-${this._uid}`;
10998
+ this.detailsId = `ui-confirm-details-${this._uid}`;
10999
+ this._describedBy = computed(() => this.svc.active()?.details.length ? `${this.msgId} ${this.detailsId}` : this.msgId, ...ngDevMode ? [{ debugName: "_describedBy" }] : /* istanbul ignore next */ []);
10721
11000
  }
10722
11001
  ngOnInit() {
10723
11002
  this.svc.registerOutlet(this);
@@ -10750,10 +11029,11 @@ var ConfirmOutletComponent = class ConfirmOutletComponent {
10750
11029
  [open]="svc.open()"
10751
11030
  [title]="svc.active()!.title"
10752
11031
  maxWidth="420px"
11032
+ [describedBy]="_describedBy()"
10753
11033
  (close)="svc.dismiss(false)">
10754
- <p class="confirm-msg" data-testid="confirm-message">{{ svc.active()!.message }}</p>
11034
+ <p class="confirm-msg" data-testid="confirm-message" [id]="msgId">{{ svc.active()!.message }}</p>
10755
11035
  @if (svc.active()!.details.length) {
10756
- <ul class="confirm-details" data-testid="confirm-details" aria-label="What will happen">
11036
+ <ul class="confirm-details" data-testid="confirm-details" aria-label="What will happen" [id]="detailsId">
10757
11037
  @for (d of svc.active()!.details; track $index) { <li>{{ d }}</li> }
10758
11038
  </ul>
10759
11039
  }
@@ -10781,6 +11061,7 @@ var ConfirmOutletComponent = class ConfirmOutletComponent {
10781
11061
  "open",
10782
11062
  "title",
10783
11063
  "description",
11064
+ "describedBy",
10784
11065
  "maxWidth",
10785
11066
  "bodyOverflow",
10786
11067
  "dialogTestId",
@@ -10846,10 +11127,11 @@ i0.ɵɵngDeclareClassMetadata({
10846
11127
  [open]="svc.open()"
10847
11128
  [title]="svc.active()!.title"
10848
11129
  maxWidth="420px"
11130
+ [describedBy]="_describedBy()"
10849
11131
  (close)="svc.dismiss(false)">
10850
- <p class="confirm-msg" data-testid="confirm-message">{{ svc.active()!.message }}</p>
11132
+ <p class="confirm-msg" data-testid="confirm-message" [id]="msgId">{{ svc.active()!.message }}</p>
10851
11133
  @if (svc.active()!.details.length) {
10852
- <ul class="confirm-details" data-testid="confirm-details" aria-label="What will happen">
11134
+ <ul class="confirm-details" data-testid="confirm-details" aria-label="What will happen" [id]="detailsId">
10853
11135
  @for (d of svc.active()!.details; track $index) { <li>{{ d }}</li> }
10854
11136
  </ul>
10855
11137
  }
@@ -12110,6 +12392,9 @@ var DataTableComponent = class DataTableComponent {
12110
12392
  this._confirmingBulk = false;
12111
12393
  this._rowDeleteTarget = signal(null, ...ngDevMode ? [{ debugName: "_rowDeleteTarget" }] : /* istanbul ignore next */ []);
12112
12394
  this._rowDeleting = signal(false, ...ngDevMode ? [{ debugName: "_rowDeleting" }] : /* istanbul ignore next */ []);
12395
+ this._confirmUid = Math.random().toString(36).slice(2, 8);
12396
+ this._confirmMsgId = `dt-confirm-msg-${this._confirmUid}`;
12397
+ this._confirmDetailsId = `dt-confirm-details-${this._confirmUid}`;
12113
12398
  this._rowDeleteConfig = signal({
12114
12399
  title: "Delete",
12115
12400
  message: "Delete this record? This cannot be undone.",
@@ -13676,10 +13961,11 @@ var DataTableComponent = class DataTableComponent {
13676
13961
  [open]="true"
13677
13962
  [title]="_rowDeleteConfig().title"
13678
13963
  maxWidth="420px"
13964
+ [describedBy]="_rowDeleteConfig().details?.length ? _confirmMsgId + ' ' + _confirmDetailsId : _confirmMsgId"
13679
13965
  (close)="_rowDeleteTarget.set(null)">
13680
- <p class="dt-confirm-msg" data-testid="confirm-message">{{ _rowDeleteConfig().message }}</p>
13966
+ <p class="dt-confirm-msg" data-testid="confirm-message" [id]="_confirmMsgId">{{ _rowDeleteConfig().message }}</p>
13681
13967
  @if (_rowDeleteConfig().details?.length) {
13682
- <ul class="dt-confirm-details" data-testid="confirm-details" aria-label="What will happen">
13968
+ <ul class="dt-confirm-details" data-testid="confirm-details" aria-label="What will happen" [id]="_confirmDetailsId">
13683
13969
  @for (d of _rowDeleteConfig().details; track $index) { <li>{{ d }}</li> }
13684
13970
  </ul>
13685
13971
  }
@@ -13981,7 +14267,16 @@ var DataTableComponent = class DataTableComponent {
13981
14267
  kind: "component",
13982
14268
  type: ToastComponent,
13983
14269
  selector: "ui-toast",
13984
- inputs: ["dismissible", "persistentLiveRegion"]
14270
+ inputs: [
14271
+ "dismissible",
14272
+ "persistentLiveRegion",
14273
+ "queue",
14274
+ "countdown",
14275
+ "maxQueued",
14276
+ "countdownLabel",
14277
+ "queueLabel"
14278
+ ],
14279
+ outputs: ["action"]
13985
14280
  },
13986
14281
  {
13987
14282
  kind: "component",
@@ -14010,6 +14305,7 @@ var DataTableComponent = class DataTableComponent {
14010
14305
  "open",
14011
14306
  "title",
14012
14307
  "description",
14308
+ "describedBy",
14013
14309
  "maxWidth",
14014
14310
  "bodyOverflow",
14015
14311
  "dialogTestId",
@@ -14750,10 +15046,11 @@ i0.ɵɵngDeclareClassMetadata({
14750
15046
  [open]="true"
14751
15047
  [title]="_rowDeleteConfig().title"
14752
15048
  maxWidth="420px"
15049
+ [describedBy]="_rowDeleteConfig().details?.length ? _confirmMsgId + ' ' + _confirmDetailsId : _confirmMsgId"
14753
15050
  (close)="_rowDeleteTarget.set(null)">
14754
- <p class="dt-confirm-msg" data-testid="confirm-message">{{ _rowDeleteConfig().message }}</p>
15051
+ <p class="dt-confirm-msg" data-testid="confirm-message" [id]="_confirmMsgId">{{ _rowDeleteConfig().message }}</p>
14755
15052
  @if (_rowDeleteConfig().details?.length) {
14756
- <ul class="dt-confirm-details" data-testid="confirm-details" aria-label="What will happen">
15053
+ <ul class="dt-confirm-details" data-testid="confirm-details" aria-label="What will happen" [id]="_confirmDetailsId">
14757
15054
  @for (d of _rowDeleteConfig().details; track $index) { <li>{{ d }}</li> }
14758
15055
  </ul>
14759
15056
  }
@@ -29236,8 +29533,8 @@ var NotesComponent = class NotesComponent {
29236
29533
  <ui-pagination [page]="page() + 1" [pageSize]="pageSize" [totalItems]="totalElements()" (pageChange)="loadPage($event - 1)" />
29237
29534
  }
29238
29535
 
29239
- <ui-modal [open]="!!deletingId()" title="Delete Note" maxWidth="420px" dialogTestId="delete-note-modal" (close)="deletingId.set(null)">
29240
- <p class="note-delete-msg" data-testid="delete-note-message">Delete this note? It is permanently removed from this record and cannot be undone.</p>
29536
+ <ui-modal [open]="!!deletingId()" title="Delete Note" maxWidth="420px" dialogTestId="delete-note-modal" describedBy="ui-note-delete-msg" (close)="deletingId.set(null)">
29537
+ <p class="note-delete-msg" id="ui-note-delete-msg" data-testid="delete-note-message">Delete this note? It is permanently removed from this record and cannot be undone.</p>
29241
29538
  <ui-button modal-footer-left variant="secondary" size="sm" data-testid="delete-note-cancel" (click)="deletingId.set(null)">Cancel</ui-button>
29242
29539
  <ui-button modal-footer-right variant="danger" size="sm" data-testid="delete-note-confirm" [loading]="saving()" (click)="deleteNote()">Delete Note</ui-button>
29243
29540
  </ui-modal>
@@ -29311,6 +29608,7 @@ var NotesComponent = class NotesComponent {
29311
29608
  "open",
29312
29609
  "title",
29313
29610
  "description",
29611
+ "describedBy",
29314
29612
  "maxWidth",
29315
29613
  "bodyOverflow",
29316
29614
  "dialogTestId",
@@ -29486,8 +29784,8 @@ i0.ɵɵngDeclareClassMetadata({
29486
29784
  <ui-pagination [page]="page() + 1" [pageSize]="pageSize" [totalItems]="totalElements()" (pageChange)="loadPage($event - 1)" />
29487
29785
  }
29488
29786
 
29489
- <ui-modal [open]="!!deletingId()" title="Delete Note" maxWidth="420px" dialogTestId="delete-note-modal" (close)="deletingId.set(null)">
29490
- <p class="note-delete-msg" data-testid="delete-note-message">Delete this note? It is permanently removed from this record and cannot be undone.</p>
29787
+ <ui-modal [open]="!!deletingId()" title="Delete Note" maxWidth="420px" dialogTestId="delete-note-modal" describedBy="ui-note-delete-msg" (close)="deletingId.set(null)">
29788
+ <p class="note-delete-msg" id="ui-note-delete-msg" data-testid="delete-note-message">Delete this note? It is permanently removed from this record and cannot be undone.</p>
29491
29789
  <ui-button modal-footer-left variant="secondary" size="sm" data-testid="delete-note-cancel" (click)="deletingId.set(null)">Cancel</ui-button>
29492
29790
  <ui-button modal-footer-right variant="danger" size="sm" data-testid="delete-note-confirm" [loading]="saving()" (click)="deleteNote()">Delete Note</ui-button>
29493
29791
  </ui-modal>
@@ -29555,6 +29853,9 @@ function provideForgerouteToast(options = {}) {
29555
29853
  return makeEnvironmentProviders([provideEnvironmentInitializer(() => {
29556
29854
  if (options.dismissible) inject(ToastService).dismissible.set(true);
29557
29855
  if (options.persistentLiveRegion) inject(ToastService).persistentLiveRegion.set(true);
29856
+ if (options.queue) inject(ToastService).queueDefault.set(true);
29857
+ if (options.countdown) inject(ToastService).countdownDefault.set(true);
29858
+ if (options.maxQueued !== void 0) inject(ToastService).maxQueued.set(Math.max(0, Math.floor(options.maxQueued)));
29558
29859
  if (!isPlatformBrowser(inject(PLATFORM_ID))) return;
29559
29860
  const doc = inject(DOCUMENT);
29560
29861
  const appRef = inject(ApplicationRef);
@@ -33587,7 +33888,16 @@ var AppShellComponent = class AppShellComponent {
33587
33888
  kind: "component",
33588
33889
  type: ToastComponent,
33589
33890
  selector: "ui-toast",
33590
- inputs: ["dismissible", "persistentLiveRegion"]
33891
+ inputs: [
33892
+ "dismissible",
33893
+ "persistentLiveRegion",
33894
+ "queue",
33895
+ "countdown",
33896
+ "maxQueued",
33897
+ "countdownLabel",
33898
+ "queueLabel"
33899
+ ],
33900
+ outputs: ["action"]
33591
33901
  },
33592
33902
  {
33593
33903
  kind: "component",
@@ -43558,7 +43868,16 @@ var CalendarComponent = class CalendarComponent {
43558
43868
  kind: "component",
43559
43869
  type: ToastComponent,
43560
43870
  selector: "ui-toast",
43561
- inputs: ["dismissible", "persistentLiveRegion"]
43871
+ inputs: [
43872
+ "dismissible",
43873
+ "persistentLiveRegion",
43874
+ "queue",
43875
+ "countdown",
43876
+ "maxQueued",
43877
+ "countdownLabel",
43878
+ "queueLabel"
43879
+ ],
43880
+ outputs: ["action"]
43562
43881
  },
43563
43882
  {
43564
43883
  kind: "directive",
@@ -44965,6 +45284,7 @@ var ImageGalleryDialogComponent = class ImageGalleryDialogComponent {
44965
45284
  "open",
44966
45285
  "title",
44967
45286
  "description",
45287
+ "describedBy",
44968
45288
  "maxWidth",
44969
45289
  "bodyOverflow",
44970
45290
  "dialogTestId",
@@ -51243,7 +51563,16 @@ var ShareMenuComponent = class ShareMenuComponent {
51243
51563
  kind: "component",
51244
51564
  type: ToastComponent,
51245
51565
  selector: "ui-toast",
51246
- inputs: ["dismissible", "persistentLiveRegion"]
51566
+ inputs: [
51567
+ "dismissible",
51568
+ "persistentLiveRegion",
51569
+ "queue",
51570
+ "countdown",
51571
+ "maxQueued",
51572
+ "countdownLabel",
51573
+ "queueLabel"
51574
+ ],
51575
+ outputs: ["action"]
51247
51576
  }],
51248
51577
  changeDetection: i0.ChangeDetectionStrategy.OnPush
51249
51578
  });
@@ -52228,6 +52557,17 @@ var ConversationsComponent = class ConversationsComponent {
52228
52557
  this.send = output();
52229
52558
  this.filesSelected = output();
52230
52559
  this.attachmentRemove = output();
52560
+ this.linkGroups = input(null, ...ngDevMode ? [{ debugName: "linkGroups" }] : /* istanbul ignore next */ []);
52561
+ this.linkLoading = input(false, ...ngDevMode ? [{ debugName: "linkLoading" }] : /* istanbul ignore next */ []);
52562
+ this.link = model(null, ...ngDevMode ? [{ debugName: "link" }] : /* istanbul ignore next */ []);
52563
+ this.linkLabel = input("Link a record", ...ngDevMode ? [{ debugName: "linkLabel" }] : /* istanbul ignore next */ []);
52564
+ this.linkPickerTitle = input("Link a Record", ...ngDevMode ? [{ debugName: "linkPickerTitle" }] : /* istanbul ignore next */ []);
52565
+ this.linkSearchPlaceholder = input("Search...", ...ngDevMode ? [{ debugName: "linkSearchPlaceholder" }] : /* istanbul ignore next */ []);
52566
+ this.linkSearchLabel = input("Search records", ...ngDevMode ? [{ debugName: "linkSearchLabel" }] : /* istanbul ignore next */ []);
52567
+ this.linkEmptyText = input("No records found.", ...ngDevMode ? [{ debugName: "linkEmptyText" }] : /* istanbul ignore next */ []);
52568
+ this.linkLoadingText = input("Loading...", ...ngDevMode ? [{ debugName: "linkLoadingText" }] : /* istanbul ignore next */ []);
52569
+ this.removeLinkLabel = input("Remove link", ...ngDevMode ? [{ debugName: "removeLinkLabel" }] : /* istanbul ignore next */ []);
52570
+ this.linkPickerOpened = output();
52231
52571
  this.mediaOpen = output();
52232
52572
  this.messageAction = output();
52233
52573
  this.loadEarlier = output();
@@ -52261,7 +52601,18 @@ var ConversationsComponent = class ConversationsComponent {
52261
52601
  this.emptyText = computed(() => this.filtering() ? this.filteredEmptyMessage() : this.canWrite() ? this.emptyMessage() : this.emptyReadOnlyMessage(), ...ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []);
52262
52602
  this.messageRows = computed(() => groupMessagesByDay(this.messages(), this.clock()), ...ngDevMode ? [{ debugName: "messageRows" }] : /* istanbul ignore next */ []);
52263
52603
  this.contextVisible = computed(() => this.hasContext() && this.contextOpen() && !!this.selected(), ...ngDevMode ? [{ debugName: "contextVisible" }] : /* istanbul ignore next */ []);
52264
- this.canSend = computed(() => !this.composerDisabled() && !this.sendBlocked() && !this.sending() && (this.draft().trim().length > 0 || !!this.attachment()) && this.draft().trim().length <= this.maxLength(), ...ngDevMode ? [{ debugName: "canSend" }] : /* istanbul ignore next */ []);
52604
+ this.canSend = computed(() => !this.composerDisabled() && !this.sendBlocked() && !this.sending() && (this.draft().trim().length > 0 || !!this.attachment() || !!this.link()) && this.draft().trim().length <= this.maxLength(), ...ngDevMode ? [{ debugName: "canSend" }] : /* istanbul ignore next */ []);
52605
+ this.pickerOpen = signal(false, ...ngDevMode ? [{ debugName: "pickerOpen" }] : /* istanbul ignore next */ []);
52606
+ this.linkQuery = signal("", ...ngDevMode ? [{ debugName: "linkQuery" }] : /* istanbul ignore next */ []);
52607
+ this.visibleLinkGroups = computed(() => {
52608
+ const q = this.linkQuery().trim().toLowerCase();
52609
+ const groups = this.linkGroups() ?? [];
52610
+ if (!q) return groups.filter((g) => g.items.length > 0);
52611
+ return groups.map((g) => ({
52612
+ ...g,
52613
+ items: g.items.filter((i) => i.label.toLowerCase().includes(q))
52614
+ })).filter((g) => g.items.length > 0);
52615
+ }, ...ngDevMode ? [{ debugName: "visibleLinkGroups" }] : /* istanbul ignore next */ []);
52265
52616
  this.editorHtml = signal("", ...ngDevMode ? [{ debugName: "editorHtml" }] : /* istanbul ignore next */ []);
52266
52617
  this.richHide = RICH_COMPOSER_HIDE;
52267
52618
  effect(() => {
@@ -52338,10 +52689,31 @@ var ConversationsComponent = class ConversationsComponent {
52338
52689
  }
52339
52690
  sendMessage() {
52340
52691
  if (!this.canSend()) return;
52341
- this.send.emit(this.richText() ? {
52692
+ const link = this.link();
52693
+ const base = this.richText() ? {
52342
52694
  text: this.draft().trim(),
52343
52695
  html: sanitizeRichTextHtml(this.editorHtml(), { mode: "paste" })
52344
- } : { text: this.draft().trim() });
52696
+ } : { text: this.draft().trim() };
52697
+ this.send.emit(link ? {
52698
+ ...base,
52699
+ link
52700
+ } : base);
52701
+ }
52702
+ openLinkPicker() {
52703
+ this.linkQuery.set("");
52704
+ this.pickerOpen.set(true);
52705
+ this.linkPickerOpened.emit();
52706
+ }
52707
+ closeLinkPicker() {
52708
+ this.pickerOpen.set(false);
52709
+ }
52710
+ pickLink(group, item) {
52711
+ this.link.set({
52712
+ type: group.type,
52713
+ id: item.id,
52714
+ label: item.label
52715
+ });
52716
+ this.pickerOpen.set(false);
52345
52717
  }
52346
52718
  clock() {
52347
52719
  return this.now() ?? /* @__PURE__ */ new Date();
@@ -52967,6 +53339,76 @@ var ConversationsComponent = class ConversationsComponent {
52967
53339
  isSignal: true,
52968
53340
  isRequired: false,
52969
53341
  transformFunction: null
53342
+ },
53343
+ linkGroups: {
53344
+ classPropertyName: "linkGroups",
53345
+ publicName: "linkGroups",
53346
+ isSignal: true,
53347
+ isRequired: false,
53348
+ transformFunction: null
53349
+ },
53350
+ linkLoading: {
53351
+ classPropertyName: "linkLoading",
53352
+ publicName: "linkLoading",
53353
+ isSignal: true,
53354
+ isRequired: false,
53355
+ transformFunction: null
53356
+ },
53357
+ link: {
53358
+ classPropertyName: "link",
53359
+ publicName: "link",
53360
+ isSignal: true,
53361
+ isRequired: false,
53362
+ transformFunction: null
53363
+ },
53364
+ linkLabel: {
53365
+ classPropertyName: "linkLabel",
53366
+ publicName: "linkLabel",
53367
+ isSignal: true,
53368
+ isRequired: false,
53369
+ transformFunction: null
53370
+ },
53371
+ linkPickerTitle: {
53372
+ classPropertyName: "linkPickerTitle",
53373
+ publicName: "linkPickerTitle",
53374
+ isSignal: true,
53375
+ isRequired: false,
53376
+ transformFunction: null
53377
+ },
53378
+ linkSearchPlaceholder: {
53379
+ classPropertyName: "linkSearchPlaceholder",
53380
+ publicName: "linkSearchPlaceholder",
53381
+ isSignal: true,
53382
+ isRequired: false,
53383
+ transformFunction: null
53384
+ },
53385
+ linkSearchLabel: {
53386
+ classPropertyName: "linkSearchLabel",
53387
+ publicName: "linkSearchLabel",
53388
+ isSignal: true,
53389
+ isRequired: false,
53390
+ transformFunction: null
53391
+ },
53392
+ linkEmptyText: {
53393
+ classPropertyName: "linkEmptyText",
53394
+ publicName: "linkEmptyText",
53395
+ isSignal: true,
53396
+ isRequired: false,
53397
+ transformFunction: null
53398
+ },
53399
+ linkLoadingText: {
53400
+ classPropertyName: "linkLoadingText",
53401
+ publicName: "linkLoadingText",
53402
+ isSignal: true,
53403
+ isRequired: false,
53404
+ transformFunction: null
53405
+ },
53406
+ removeLinkLabel: {
53407
+ classPropertyName: "removeLinkLabel",
53408
+ publicName: "removeLinkLabel",
53409
+ isSignal: true,
53410
+ isRequired: false,
53411
+ transformFunction: null
52970
53412
  }
52971
53413
  },
52972
53414
  outputs: {
@@ -52986,6 +53428,8 @@ var ConversationsComponent = class ConversationsComponent {
52986
53428
  send: "send",
52987
53429
  filesSelected: "filesSelected",
52988
53430
  attachmentRemove: "attachmentRemove",
53431
+ link: "linkChange",
53432
+ linkPickerOpened: "linkPickerOpened",
52989
53433
  mediaOpen: "mediaOpen",
52990
53434
  messageAction: "messageAction",
52991
53435
  loadEarlier: "loadEarlier",
@@ -53201,6 +53645,13 @@ var ConversationsComponent = class ConversationsComponent {
53201
53645
  }
53202
53646
  @if (row.message.bodyHtml) { <ui-conversation-rich-body [html]="row.message.bodyHtml" data-testid="msg-rich-body" /> }
53203
53647
  @else if (row.message.body) { <p class="cv-body">{{ row.message.body }}</p> }
53648
+ @if (row.message.link; as msgLink) {
53649
+ @if (msgLink.url) {
53650
+ <a class="cv-link-chip" [href]="msgLink.url" data-testid="msg-link"><svg class="cv-link-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1.5 1.5"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.5-1.5"/></svg><span class="cv-link-label">{{ msgLink.label }}</span></a>
53651
+ } @else {
53652
+ <span class="cv-link-chip" data-testid="msg-link"><svg class="cv-link-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1.5 1.5"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.5-1.5"/></svg><span class="cv-link-label">{{ msgLink.label }}</span></span>
53653
+ }
53654
+ }
53204
53655
  <span class="cv-meta">
53205
53656
  @if (row.message.sender) { <span class="cv-sender" data-testid="msg-sender">{{ row.message.sender }}</span> }
53206
53657
  {{ time(row.message) }}
@@ -53233,7 +53684,20 @@ var ConversationsComponent = class ConversationsComponent {
53233
53684
  </ui-button>
53234
53685
  </div>
53235
53686
  }
53687
+ @if (link(); as chosen) {
53688
+ <div class="cv-link-preview" data-testid="link-preview">
53689
+ <span class="cv-link-chip cv-link-chip-compose"><svg class="cv-link-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1.5 1.5"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.5-1.5"/></svg><span class="cv-link-label" data-testid="link-preview-label">{{ chosen.label }}</span></span>
53690
+ <ui-button class="cv-link-remove" variant="icon-danger" [ariaLabel]="removeLinkLabel()" [disabled]="sending()" (click)="link.set(null)" data-testid="link-remove">
53691
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
53692
+ </ui-button>
53693
+ </div>
53694
+ }
53236
53695
  <div class="cv-compose-row">
53696
+ @if (linkGroups() !== null) {
53697
+ <ui-button class="cv-link-btn" variant="icon" [ariaLabel]="linkLabel()" [uiTooltip]="linkLabel()" [disabled]="sending() || composerDisabled()" (click)="openLinkPicker()" data-testid="link-btn">
53698
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1.5 1.5"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.5-1.5"/></svg>
53699
+ </ui-button>
53700
+ }
53237
53701
  @if (attachEnabled()) {
53238
53702
  <ui-button class="cv-attach-btn" variant="icon" [ariaLabel]="attachLabel()" [disabled]="uploading() || composerDisabled()" [loading]="uploading()" (click)="openFilePicker()" data-testid="attach-btn">
53239
53703
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
@@ -53266,6 +53730,28 @@ var ConversationsComponent = class ConversationsComponent {
53266
53730
  }
53267
53731
  </section>
53268
53732
 
53733
+ @if (linkGroups() !== null) {
53734
+ <ui-modal [open]="pickerOpen()" [title]="linkPickerTitle()" maxWidth="480px" bodyOverflow="hidden" dialogTestId="link-picker" (close)="closeLinkPicker()">
53735
+ <ui-search-input [placeholder]="linkSearchPlaceholder()" [ariaLabel]="linkSearchLabel()" [(value)]="linkQuery" data-testid="link-search" />
53736
+ <div class="cv-link-list" data-testid="link-list">
53737
+ @if (linkLoading()) {
53738
+ <p class="cv-link-note" role="status" data-testid="link-loading">{{ linkLoadingText() }}</p>
53739
+ } @else if (visibleLinkGroups().length === 0) {
53740
+ <p class="cv-link-note" data-testid="link-empty">{{ linkEmptyText() }}</p>
53741
+ } @else {
53742
+ @for (group of visibleLinkGroups(); track group.type) {
53743
+ <div class="cv-link-section" role="group" [attr.aria-label]="group.label" data-testid="link-section">
53744
+ <div class="cv-link-section-label" aria-hidden="true">{{ group.label }}</div>
53745
+ @for (item of group.items; track item.id) {
53746
+ <button type="button" class="cv-link-item" data-testid="link-item" (click)="pickLink(group, item)">{{ item.label }}</button>
53747
+ }
53748
+ </div>
53749
+ }
53750
+ }
53751
+ </div>
53752
+ </ui-modal>
53753
+ }
53754
+
53269
53755
  <!-- RIGHT: contact panel (the host's content) -->
53270
53756
  @if (contextVisible()) {
53271
53757
  <aside class="cv-context" #contextPanel tabindex="-1" [attr.aria-label]="contextTitle()" (keydown.escape)="closeContext(true)" data-testid="context-panel">
@@ -53281,7 +53767,7 @@ var ConversationsComponent = class ConversationsComponent {
53281
53767
  </div>
53282
53768
  `,
53283
53769
  isInline: true,
53284
- styles: [":host{display:flex;flex:1;min-height:0;min-width:0}.cv-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.cv-layout{display:grid;grid-template-columns:340px 1fr;flex:1;min-height:0;min-width:0;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px #0000000f;border:1px solid #e2e8f0}.cv-layout.has-context{grid-template-columns:340px 1fr 280px}.cv-list-panel{display:flex;flex-direction:column;min-height:0;border-right:1px solid #e2e8f0;background:#f8fafc;overflow:hidden}.cv-list-header{display:flex;align-items:center;justify-content:space-between;padding:20px 20px 12px}.cv-list-title{margin:0;font-size:1.125rem;font-weight:700;color:#1e293b}.cv-search,.cv-filters{padding:0 16px 12px}.cv-list{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}.cv-item{display:flex;gap:12px;padding:14px 16px;border-bottom:1px solid #f1f5f9;transition:background .15s}.cv-item.unread{background:#fffbeb}.cv-item.selected{background:#eff6ff;box-shadow:inset 3px 0 #2563eb}.cv-item-info{flex:1;min-width:0}.cv-row-arrow{flex:0 0 auto;align-self:center;color:#94a3b8}.cv-item-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.cv-name{font-size:.875rem;font-weight:600;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cv-time{font-size:.6875rem;color:#64748b;flex-shrink:0;margin-left:8px}.cv-subtitle{font-size:.6875rem;color:#64748b;display:block;margin-bottom:2px}.cv-item-bottom{display:flex;justify-content:space-between;align-items:center;gap:8px}.cv-preview{margin:0;font-size:.8125rem;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.cv-item-right{display:flex;align-items:center;gap:6px;flex-shrink:0}.cv-delete-btn{opacity:0;transition:opacity .15s}.cv-item:hover .cv-delete-btn,.cv-item:focus-within .cv-delete-btn,.cv-delete-btn:focus-within{opacity:1}@media(hover:none){.cv-delete-btn{opacity:1}}.cv-loading{padding:8px 0}.cv-skeleton{display:flex;gap:12px;align-items:flex-start;padding:14px 16px;border-bottom:1px solid #f1f5f9}.cv-skel-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px;padding-top:2px}.cv-skel-row{display:flex;justify-content:space-between;gap:8px}.cv-error{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 16px;text-align:center}.cv-thread-panel{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden;background:#fff}.cv-thread-header{display:flex;align-items:center;gap:12px;padding:16px 20px;border-bottom:1px solid #e2e8f0;background:#fff}.cv-back-btn{display:none}.cv-thread-header-info{display:flex;align-items:center;gap:12px;flex:1;min-width:0}.cv-thread-header-text{min-width:0;display:flex;flex-direction:column}.cv-thread-name{margin:0;font-size:.9375rem;font-weight:600;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cv-thread-name:focus{outline:none}.cv-thread-name:focus-visible{outline:2px solid #2563eb;outline-offset:2px;border-radius:4px}.cv-thread-subtitle{font-size:.75rem;color:#64748b}.cv-thread-actions{display:flex;align-items:center;gap:8px}.cv-messages{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;padding:20px 24px;display:flex;flex-direction:column;gap:4px;background:#f8fafc}.cv-messages:focus-visible{outline:2px solid #2563eb;outline-offset:-2px}.cv-messages-loading{display:flex;flex-direction:column;gap:12px;padding:20px 0}.cv-msg-skeleton{display:flex}.cv-msg-skeleton.outbound{justify-content:flex-end}.cv-messages-error{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 16px;text-align:center}.cv-load-earlier{display:flex;justify-content:center;padding:4px 0 12px}.cv-sender{font-weight:600}.cv-msg-action{padding:0;border:0;background:none;color:inherit;font:inherit;text-decoration:underline;cursor:pointer}.cv-msg-action:focus-visible{outline:2px solid currentColor;outline-offset:2px;border-radius:2px}.cv-messages-empty{display:flex;align-items:center;justify-content:center;flex:1}.cv-messages-empty p{margin:0;font-size:.875rem;color:#64748b;text-align:center}.cv-date-separator{display:flex;align-items:center;justify-content:center;padding:16px 0 8px}.cv-date-separator span{padding:4px 14px;border-radius:12px;background:#e2e8f0;font-size:.6875rem;font-weight:600;color:#475569;text-transform:uppercase;letter-spacing:.3px}.cv-bubble{max-width:70%;padding:10px 14px;border-radius:16px;position:relative;word-break:break-word;overflow-wrap:break-word}.cv-bubble.inbound{align-self:flex-start;background:#fff;border:1px solid #e2e8f0;border-bottom-left-radius:4px}.cv-bubble.inbound .cv-body,.cv-bubble.inbound .cv-rich{color:#1e293b}.cv-bubble.inbound .cv-meta{color:#64748b}.cv-bubble.outbound{align-self:flex-end;background:#2563eb;border-bottom-right-radius:4px}.cv-bubble.outbound .cv-body,.cv-bubble.outbound .cv-rich,.cv-bubble.outbound .cv-meta{color:#fff}@media(prefers-reduced-motion:no-preference){.cv-bubble{animation:cv-appear .2s ease}.cv-context{animation:cv-slide-in .25s ease}}.cv-body{margin:0;font-size:.875rem;line-height:1.45;word-break:break-word;white-space:pre-wrap}.cv-media{margin-bottom:6px}.cv-image-btn{display:block;padding:0;border:0;background:none;cursor:pointer;border-radius:8px}.cv-image-btn:focus-visible{outline:2px solid #fff;outline-offset:2px;box-shadow:0 0 0 4px #2563eb}.cv-image{display:block;max-width:240px;max-height:240px;border-radius:8px;object-fit:cover}.cv-video{max-width:240px;max-height:200px;border-radius:8px}.cv-file-link{font-size:.8125rem;color:#fff;text-decoration:underline}.cv-bubble.inbound .cv-file-link{color:#2563eb}.cv-meta{display:flex;align-items:center;gap:6px;justify-content:flex-end;font-size:.6875rem;margin-top:4px}.cv-status{font-weight:500}.cv-status.failed{color:#fff;font-weight:700}.cv-bubble.inbound .cv-status.failed{color:#dc2626}.cv-compose{display:flex;flex-direction:column;padding:12px 20px 16px;padding-bottom:calc(16px + env(safe-area-inset-bottom,0px));border-top:1px solid #e2e8f0;background:#fff}.cv-compose-row{display:flex;align-items:flex-end;gap:10px}.cv-compose-input{flex:1;min-width:0}.cv-attach-btn,.cv-send-btn{flex-shrink:0}.cv-attachment{display:flex;align-items:center;gap:8px;padding:8px 12px;margin-bottom:8px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px}.cv-attachment-thumb{width:48px;height:48px;object-fit:cover;border-radius:6px}.cv-attachment-name{font-size:.8125rem;color:#475569;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cv-attachment-remove{flex-shrink:0}.cv-compose-status{margin:4px 0 0;font-size:.8125rem;color:#64748b}.cv-compose-status:empty{margin:0}.cv-context-backdrop{display:none}.cv-context{width:100%;min-width:0;border-left:1px solid #e2e8f0;background:#fff;display:flex;flex-direction:column;overflow-y:auto}.cv-context-header{display:flex;justify-content:space-between;align-items:center;padding:20px;border-bottom:1px solid #e2e8f0}.cv-context-header h3{margin:0;font-size:.875rem;font-weight:700;color:#1e293b}.cv-context-body{padding:20px;display:flex;flex-direction:column;gap:16px}@keyframes cv-appear{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes cv-slide-in{0%{transform:translate(100%)}to{transform:translate(0)}}@media(max-width:1100px){.cv-layout.has-context{grid-template-columns:280px 1fr 240px}}@media(max-width:768px){.cv-layout,.cv-layout.has-context{grid-template-columns:1fr;height:100%}.cv-hidden-mobile{display:none!important}.cv-back-btn{display:inline-flex}.cv-context-backdrop{display:block;position:fixed;inset:0;background:#00000059;z-index:99}.cv-context{position:fixed;top:0;right:0;bottom:0;width:300px;max-width:85vw;z-index:100;box-shadow:-4px 0 20px #0000001a}.cv-bubble{max-width:85%}.cv-messages,.cv-thread-header{padding:12px 16px}}@media(max-width:480px){.cv-thread-subtitle{display:none}.cv-compose{padding:8px 12px 12px;padding-bottom:calc(12px + env(safe-area-inset-bottom,0px))}.cv-compose-row{flex-wrap:wrap}.cv-compose-input{order:-1;flex:1 1 100%}.cv-send-btn{margin-inline-start:auto}.cv-list-header{padding:12px 16px 10px}.cv-context{width:100%;max-width:100vw}}\n"],
53770
+ styles: [":host{display:flex;flex:1;min-height:0;min-width:0}.cv-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.cv-layout{display:grid;grid-template-columns:340px 1fr;flex:1;min-height:0;min-width:0;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px #0000000f;border:1px solid #e2e8f0}.cv-layout.has-context{grid-template-columns:340px 1fr 280px}.cv-list-panel{display:flex;flex-direction:column;min-height:0;border-right:1px solid #e2e8f0;background:#f8fafc;overflow:hidden}.cv-list-header{display:flex;align-items:center;justify-content:space-between;padding:20px 20px 12px}.cv-list-title{margin:0;font-size:1.125rem;font-weight:700;color:#1e293b}.cv-search,.cv-filters{padding:0 16px 12px}.cv-list{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}.cv-item{display:flex;gap:12px;padding:14px 16px;border-bottom:1px solid #f1f5f9;transition:background .15s}.cv-item.unread{background:#fffbeb}.cv-item.selected{background:#eff6ff;box-shadow:inset 3px 0 #2563eb}.cv-item-info{flex:1;min-width:0}.cv-row-arrow{flex:0 0 auto;align-self:center;color:#94a3b8}.cv-item-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.cv-name{font-size:.875rem;font-weight:600;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cv-time{font-size:.6875rem;color:#64748b;flex-shrink:0;margin-left:8px}.cv-subtitle{font-size:.6875rem;color:#64748b;display:block;margin-bottom:2px}.cv-item-bottom{display:flex;justify-content:space-between;align-items:center;gap:8px}.cv-preview{margin:0;font-size:.8125rem;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.cv-item-right{display:flex;align-items:center;gap:6px;flex-shrink:0}.cv-delete-btn{opacity:0;transition:opacity .15s}.cv-item:hover .cv-delete-btn,.cv-item:focus-within .cv-delete-btn,.cv-delete-btn:focus-within{opacity:1}@media(hover:none){.cv-delete-btn{opacity:1}}.cv-loading{padding:8px 0}.cv-skeleton{display:flex;gap:12px;align-items:flex-start;padding:14px 16px;border-bottom:1px solid #f1f5f9}.cv-skel-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px;padding-top:2px}.cv-skel-row{display:flex;justify-content:space-between;gap:8px}.cv-error{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 16px;text-align:center}.cv-thread-panel{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden;background:#fff}.cv-thread-header{display:flex;align-items:center;gap:12px;padding:16px 20px;border-bottom:1px solid #e2e8f0;background:#fff}.cv-back-btn{display:none}.cv-thread-header-info{display:flex;align-items:center;gap:12px;flex:1;min-width:0}.cv-thread-header-text{min-width:0;display:flex;flex-direction:column}.cv-thread-name{margin:0;font-size:.9375rem;font-weight:600;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cv-thread-name:focus{outline:none}.cv-thread-name:focus-visible{outline:2px solid #2563eb;outline-offset:2px;border-radius:4px}.cv-thread-subtitle{font-size:.75rem;color:#64748b}.cv-thread-actions{display:flex;align-items:center;gap:8px}.cv-messages{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;padding:20px 24px;display:flex;flex-direction:column;gap:4px;background:#f8fafc}.cv-messages:focus-visible{outline:2px solid #2563eb;outline-offset:-2px}.cv-messages-loading{display:flex;flex-direction:column;gap:12px;padding:20px 0}.cv-msg-skeleton{display:flex}.cv-msg-skeleton.outbound{justify-content:flex-end}.cv-messages-error{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 16px;text-align:center}.cv-load-earlier{display:flex;justify-content:center;padding:4px 0 12px}.cv-sender{font-weight:600}.cv-link-chip{display:inline-flex;align-items:center;gap:6px;max-width:100%;margin-top:6px;padding:3px 10px;border-radius:999px;border:1px solid transparent;font-size:.8rem;font-weight:600;text-decoration:none}.cv-link-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.cv-link-icon{flex:none}.cv-bubble.inbound .cv-link-chip{background:#eff6ff;border-color:#bfdbfe;color:#1d4ed8}.cv-bubble.outbound .cv-link-chip{background:#ffffff2e;border-color:#ffffff73;color:#fff}.cv-link-chip:is(a):hover{filter:brightness(.96);text-decoration:underline}.cv-link-chip:is(a):focus-visible{outline:2px solid currentColor;outline-offset:2px}.cv-link-preview{display:flex;align-items:center;gap:4px;margin-bottom:8px}.cv-link-chip-compose{margin-top:0;background:#eff6ff;border-color:#bfdbfe;color:#1d4ed8}.cv-link-list{display:flex;flex-direction:column;gap:4px;margin-top:12px;max-height:320px;overflow-y:auto}.cv-link-section-label{padding:8px 4px 4px;font-size:.7rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:#64748b}.cv-link-item{display:block;width:100%;padding:10px 12px;border:0;border-radius:8px;background:none;text-align:start;font:inherit;font-size:.875rem;color:#1e293b;cursor:pointer}.cv-link-item:hover{background:#f1f5f9}.cv-link-item:focus-visible{outline:2px solid #2563eb;outline-offset:-2px}.cv-link-note{margin:16px 0;text-align:center;font-size:.875rem;color:#64748b}@media(pointer:coarse),(max-width:480px){.cv-link-item{min-height:44px}}.cv-msg-action{padding:0;border:0;background:none;color:inherit;font:inherit;text-decoration:underline;cursor:pointer}.cv-msg-action:focus-visible{outline:2px solid currentColor;outline-offset:2px;border-radius:2px}.cv-messages-empty{display:flex;align-items:center;justify-content:center;flex:1}.cv-messages-empty p{margin:0;font-size:.875rem;color:#64748b;text-align:center}.cv-date-separator{display:flex;align-items:center;justify-content:center;padding:16px 0 8px}.cv-date-separator span{padding:4px 14px;border-radius:12px;background:#e2e8f0;font-size:.6875rem;font-weight:600;color:#475569;text-transform:uppercase;letter-spacing:.3px}.cv-bubble{max-width:70%;padding:10px 14px;border-radius:16px;position:relative;word-break:break-word;overflow-wrap:break-word}.cv-bubble.inbound{align-self:flex-start;background:#fff;border:1px solid #e2e8f0;border-bottom-left-radius:4px}.cv-bubble.inbound .cv-body,.cv-bubble.inbound .cv-rich{color:#1e293b}.cv-bubble.inbound .cv-meta{color:#64748b}.cv-bubble.outbound{align-self:flex-end;background:#2563eb;border-bottom-right-radius:4px}.cv-bubble.outbound .cv-body,.cv-bubble.outbound .cv-rich,.cv-bubble.outbound .cv-meta{color:#fff}@media(prefers-reduced-motion:no-preference){.cv-bubble{animation:cv-appear .2s ease}.cv-context{animation:cv-slide-in .25s ease}}.cv-body{margin:0;font-size:.875rem;line-height:1.45;word-break:break-word;white-space:pre-wrap}.cv-media{margin-bottom:6px}.cv-image-btn{display:block;padding:0;border:0;background:none;cursor:pointer;border-radius:8px}.cv-image-btn:focus-visible{outline:2px solid #fff;outline-offset:2px;box-shadow:0 0 0 4px #2563eb}.cv-image{display:block;max-width:240px;max-height:240px;border-radius:8px;object-fit:cover}.cv-video{max-width:240px;max-height:200px;border-radius:8px}.cv-file-link{font-size:.8125rem;color:#fff;text-decoration:underline}.cv-bubble.inbound .cv-file-link{color:#2563eb}.cv-meta{display:flex;align-items:center;gap:6px;justify-content:flex-end;font-size:.6875rem;margin-top:4px}.cv-status{font-weight:500}.cv-status.failed{color:#fff;font-weight:700}.cv-bubble.inbound .cv-status.failed{color:#dc2626}.cv-compose{display:flex;flex-direction:column;padding:12px 20px 16px;padding-bottom:calc(16px + env(safe-area-inset-bottom,0px));border-top:1px solid #e2e8f0;background:#fff}.cv-compose-row{display:flex;align-items:flex-end;gap:10px}.cv-compose-input{flex:1;min-width:0}.cv-attach-btn,.cv-send-btn{flex-shrink:0}.cv-attachment{display:flex;align-items:center;gap:8px;padding:8px 12px;margin-bottom:8px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px}.cv-attachment-thumb{width:48px;height:48px;object-fit:cover;border-radius:6px}.cv-attachment-name{font-size:.8125rem;color:#475569;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cv-attachment-remove{flex-shrink:0}.cv-compose-status{margin:4px 0 0;font-size:.8125rem;color:#64748b}.cv-compose-status:empty{margin:0}.cv-context-backdrop{display:none}.cv-context{width:100%;min-width:0;border-left:1px solid #e2e8f0;background:#fff;display:flex;flex-direction:column;overflow-y:auto}.cv-context-header{display:flex;justify-content:space-between;align-items:center;padding:20px;border-bottom:1px solid #e2e8f0}.cv-context-header h3{margin:0;font-size:.875rem;font-weight:700;color:#1e293b}.cv-context-body{padding:20px;display:flex;flex-direction:column;gap:16px}@keyframes cv-appear{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes cv-slide-in{0%{transform:translate(100%)}to{transform:translate(0)}}@media(max-width:1100px){.cv-layout.has-context{grid-template-columns:280px 1fr 240px}}@media(max-width:768px){.cv-layout,.cv-layout.has-context{grid-template-columns:1fr;height:100%}.cv-hidden-mobile{display:none!important}.cv-back-btn{display:inline-flex}.cv-context-backdrop{display:block;position:fixed;inset:0;background:#00000059;z-index:99}.cv-context{position:fixed;top:0;right:0;bottom:0;width:300px;max-width:85vw;z-index:100;box-shadow:-4px 0 20px #0000001a}.cv-bubble{max-width:85%}.cv-messages,.cv-thread-header{padding:12px 16px}}@media(max-width:480px){.cv-thread-subtitle{display:none}.cv-compose{padding:8px 12px 12px;padding-bottom:calc(12px + env(safe-area-inset-bottom,0px))}.cv-compose-row{flex-wrap:wrap}.cv-compose-input{order:-1;flex:1 1 100%}.cv-send-btn{margin-inline-start:auto}.cv-list-header{padding:12px 16px 10px}.cv-context{width:100%;max-width:100vw}}\n"],
53285
53771
  dependencies: [
53286
53772
  {
53287
53773
  kind: "ngmodule",
@@ -53567,6 +54053,28 @@ var ConversationsComponent = class ConversationsComponent {
53567
54053
  type: ConversationRichBodyComponent,
53568
54054
  selector: "ui-conversation-rich-body",
53569
54055
  inputs: ["html"]
54056
+ },
54057
+ {
54058
+ kind: "component",
54059
+ type: ModalComponent,
54060
+ selector: "ui-modal",
54061
+ inputs: [
54062
+ "open",
54063
+ "title",
54064
+ "description",
54065
+ "describedBy",
54066
+ "maxWidth",
54067
+ "bodyOverflow",
54068
+ "dialogTestId",
54069
+ "hideClose",
54070
+ "closable",
54071
+ "layout",
54072
+ "closeOnBackdrop",
54073
+ "closeOnEscape",
54074
+ "busy",
54075
+ "returnFocusTo"
54076
+ ],
54077
+ outputs: ["close"]
53570
54078
  }
53571
54079
  ],
53572
54080
  changeDetection: i0.ChangeDetectionStrategy.OnPush
@@ -53600,7 +54108,8 @@ i0.ɵɵngDeclareClassMetadata({
53600
54108
  SkeletonComponent,
53601
54109
  TextareaInputComponent,
53602
54110
  TooltipDirective,
53603
- ConversationRichBodyComponent
54111
+ ConversationRichBodyComponent,
54112
+ ModalComponent
53604
54113
  ],
53605
54114
  template: `
53606
54115
  <div class="cv-layout" [class.has-context]="contextVisible()" data-testid="conversations">
@@ -53781,6 +54290,13 @@ i0.ɵɵngDeclareClassMetadata({
53781
54290
  }
53782
54291
  @if (row.message.bodyHtml) { <ui-conversation-rich-body [html]="row.message.bodyHtml" data-testid="msg-rich-body" /> }
53783
54292
  @else if (row.message.body) { <p class="cv-body">{{ row.message.body }}</p> }
54293
+ @if (row.message.link; as msgLink) {
54294
+ @if (msgLink.url) {
54295
+ <a class="cv-link-chip" [href]="msgLink.url" data-testid="msg-link"><svg class="cv-link-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1.5 1.5"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.5-1.5"/></svg><span class="cv-link-label">{{ msgLink.label }}</span></a>
54296
+ } @else {
54297
+ <span class="cv-link-chip" data-testid="msg-link"><svg class="cv-link-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1.5 1.5"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.5-1.5"/></svg><span class="cv-link-label">{{ msgLink.label }}</span></span>
54298
+ }
54299
+ }
53784
54300
  <span class="cv-meta">
53785
54301
  @if (row.message.sender) { <span class="cv-sender" data-testid="msg-sender">{{ row.message.sender }}</span> }
53786
54302
  {{ time(row.message) }}
@@ -53813,7 +54329,20 @@ i0.ɵɵngDeclareClassMetadata({
53813
54329
  </ui-button>
53814
54330
  </div>
53815
54331
  }
54332
+ @if (link(); as chosen) {
54333
+ <div class="cv-link-preview" data-testid="link-preview">
54334
+ <span class="cv-link-chip cv-link-chip-compose"><svg class="cv-link-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1.5 1.5"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.5-1.5"/></svg><span class="cv-link-label" data-testid="link-preview-label">{{ chosen.label }}</span></span>
54335
+ <ui-button class="cv-link-remove" variant="icon-danger" [ariaLabel]="removeLinkLabel()" [disabled]="sending()" (click)="link.set(null)" data-testid="link-remove">
54336
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
54337
+ </ui-button>
54338
+ </div>
54339
+ }
53816
54340
  <div class="cv-compose-row">
54341
+ @if (linkGroups() !== null) {
54342
+ <ui-button class="cv-link-btn" variant="icon" [ariaLabel]="linkLabel()" [uiTooltip]="linkLabel()" [disabled]="sending() || composerDisabled()" (click)="openLinkPicker()" data-testid="link-btn">
54343
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1.5 1.5"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.5-1.5"/></svg>
54344
+ </ui-button>
54345
+ }
53817
54346
  @if (attachEnabled()) {
53818
54347
  <ui-button class="cv-attach-btn" variant="icon" [ariaLabel]="attachLabel()" [disabled]="uploading() || composerDisabled()" [loading]="uploading()" (click)="openFilePicker()" data-testid="attach-btn">
53819
54348
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
@@ -53846,6 +54375,28 @@ i0.ɵɵngDeclareClassMetadata({
53846
54375
  }
53847
54376
  </section>
53848
54377
 
54378
+ @if (linkGroups() !== null) {
54379
+ <ui-modal [open]="pickerOpen()" [title]="linkPickerTitle()" maxWidth="480px" bodyOverflow="hidden" dialogTestId="link-picker" (close)="closeLinkPicker()">
54380
+ <ui-search-input [placeholder]="linkSearchPlaceholder()" [ariaLabel]="linkSearchLabel()" [(value)]="linkQuery" data-testid="link-search" />
54381
+ <div class="cv-link-list" data-testid="link-list">
54382
+ @if (linkLoading()) {
54383
+ <p class="cv-link-note" role="status" data-testid="link-loading">{{ linkLoadingText() }}</p>
54384
+ } @else if (visibleLinkGroups().length === 0) {
54385
+ <p class="cv-link-note" data-testid="link-empty">{{ linkEmptyText() }}</p>
54386
+ } @else {
54387
+ @for (group of visibleLinkGroups(); track group.type) {
54388
+ <div class="cv-link-section" role="group" [attr.aria-label]="group.label" data-testid="link-section">
54389
+ <div class="cv-link-section-label" aria-hidden="true">{{ group.label }}</div>
54390
+ @for (item of group.items; track item.id) {
54391
+ <button type="button" class="cv-link-item" data-testid="link-item" (click)="pickLink(group, item)">{{ item.label }}</button>
54392
+ }
54393
+ </div>
54394
+ }
54395
+ }
54396
+ </div>
54397
+ </ui-modal>
54398
+ }
54399
+
53849
54400
  <!-- RIGHT: contact panel (the host's content) -->
53850
54401
  @if (contextVisible()) {
53851
54402
  <aside class="cv-context" #contextPanel tabindex="-1" [attr.aria-label]="contextTitle()" (keydown.escape)="closeContext(true)" data-testid="context-panel">
@@ -53860,7 +54411,7 @@ i0.ɵɵngDeclareClassMetadata({
53860
54411
  }
53861
54412
  </div>
53862
54413
  `,
53863
- styles: [":host{display:flex;flex:1;min-height:0;min-width:0}.cv-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.cv-layout{display:grid;grid-template-columns:340px 1fr;flex:1;min-height:0;min-width:0;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px #0000000f;border:1px solid #e2e8f0}.cv-layout.has-context{grid-template-columns:340px 1fr 280px}.cv-list-panel{display:flex;flex-direction:column;min-height:0;border-right:1px solid #e2e8f0;background:#f8fafc;overflow:hidden}.cv-list-header{display:flex;align-items:center;justify-content:space-between;padding:20px 20px 12px}.cv-list-title{margin:0;font-size:1.125rem;font-weight:700;color:#1e293b}.cv-search,.cv-filters{padding:0 16px 12px}.cv-list{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}.cv-item{display:flex;gap:12px;padding:14px 16px;border-bottom:1px solid #f1f5f9;transition:background .15s}.cv-item.unread{background:#fffbeb}.cv-item.selected{background:#eff6ff;box-shadow:inset 3px 0 #2563eb}.cv-item-info{flex:1;min-width:0}.cv-row-arrow{flex:0 0 auto;align-self:center;color:#94a3b8}.cv-item-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.cv-name{font-size:.875rem;font-weight:600;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cv-time{font-size:.6875rem;color:#64748b;flex-shrink:0;margin-left:8px}.cv-subtitle{font-size:.6875rem;color:#64748b;display:block;margin-bottom:2px}.cv-item-bottom{display:flex;justify-content:space-between;align-items:center;gap:8px}.cv-preview{margin:0;font-size:.8125rem;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.cv-item-right{display:flex;align-items:center;gap:6px;flex-shrink:0}.cv-delete-btn{opacity:0;transition:opacity .15s}.cv-item:hover .cv-delete-btn,.cv-item:focus-within .cv-delete-btn,.cv-delete-btn:focus-within{opacity:1}@media(hover:none){.cv-delete-btn{opacity:1}}.cv-loading{padding:8px 0}.cv-skeleton{display:flex;gap:12px;align-items:flex-start;padding:14px 16px;border-bottom:1px solid #f1f5f9}.cv-skel-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px;padding-top:2px}.cv-skel-row{display:flex;justify-content:space-between;gap:8px}.cv-error{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 16px;text-align:center}.cv-thread-panel{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden;background:#fff}.cv-thread-header{display:flex;align-items:center;gap:12px;padding:16px 20px;border-bottom:1px solid #e2e8f0;background:#fff}.cv-back-btn{display:none}.cv-thread-header-info{display:flex;align-items:center;gap:12px;flex:1;min-width:0}.cv-thread-header-text{min-width:0;display:flex;flex-direction:column}.cv-thread-name{margin:0;font-size:.9375rem;font-weight:600;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cv-thread-name:focus{outline:none}.cv-thread-name:focus-visible{outline:2px solid #2563eb;outline-offset:2px;border-radius:4px}.cv-thread-subtitle{font-size:.75rem;color:#64748b}.cv-thread-actions{display:flex;align-items:center;gap:8px}.cv-messages{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;padding:20px 24px;display:flex;flex-direction:column;gap:4px;background:#f8fafc}.cv-messages:focus-visible{outline:2px solid #2563eb;outline-offset:-2px}.cv-messages-loading{display:flex;flex-direction:column;gap:12px;padding:20px 0}.cv-msg-skeleton{display:flex}.cv-msg-skeleton.outbound{justify-content:flex-end}.cv-messages-error{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 16px;text-align:center}.cv-load-earlier{display:flex;justify-content:center;padding:4px 0 12px}.cv-sender{font-weight:600}.cv-msg-action{padding:0;border:0;background:none;color:inherit;font:inherit;text-decoration:underline;cursor:pointer}.cv-msg-action:focus-visible{outline:2px solid currentColor;outline-offset:2px;border-radius:2px}.cv-messages-empty{display:flex;align-items:center;justify-content:center;flex:1}.cv-messages-empty p{margin:0;font-size:.875rem;color:#64748b;text-align:center}.cv-date-separator{display:flex;align-items:center;justify-content:center;padding:16px 0 8px}.cv-date-separator span{padding:4px 14px;border-radius:12px;background:#e2e8f0;font-size:.6875rem;font-weight:600;color:#475569;text-transform:uppercase;letter-spacing:.3px}.cv-bubble{max-width:70%;padding:10px 14px;border-radius:16px;position:relative;word-break:break-word;overflow-wrap:break-word}.cv-bubble.inbound{align-self:flex-start;background:#fff;border:1px solid #e2e8f0;border-bottom-left-radius:4px}.cv-bubble.inbound .cv-body,.cv-bubble.inbound .cv-rich{color:#1e293b}.cv-bubble.inbound .cv-meta{color:#64748b}.cv-bubble.outbound{align-self:flex-end;background:#2563eb;border-bottom-right-radius:4px}.cv-bubble.outbound .cv-body,.cv-bubble.outbound .cv-rich,.cv-bubble.outbound .cv-meta{color:#fff}@media(prefers-reduced-motion:no-preference){.cv-bubble{animation:cv-appear .2s ease}.cv-context{animation:cv-slide-in .25s ease}}.cv-body{margin:0;font-size:.875rem;line-height:1.45;word-break:break-word;white-space:pre-wrap}.cv-media{margin-bottom:6px}.cv-image-btn{display:block;padding:0;border:0;background:none;cursor:pointer;border-radius:8px}.cv-image-btn:focus-visible{outline:2px solid #fff;outline-offset:2px;box-shadow:0 0 0 4px #2563eb}.cv-image{display:block;max-width:240px;max-height:240px;border-radius:8px;object-fit:cover}.cv-video{max-width:240px;max-height:200px;border-radius:8px}.cv-file-link{font-size:.8125rem;color:#fff;text-decoration:underline}.cv-bubble.inbound .cv-file-link{color:#2563eb}.cv-meta{display:flex;align-items:center;gap:6px;justify-content:flex-end;font-size:.6875rem;margin-top:4px}.cv-status{font-weight:500}.cv-status.failed{color:#fff;font-weight:700}.cv-bubble.inbound .cv-status.failed{color:#dc2626}.cv-compose{display:flex;flex-direction:column;padding:12px 20px 16px;padding-bottom:calc(16px + env(safe-area-inset-bottom,0px));border-top:1px solid #e2e8f0;background:#fff}.cv-compose-row{display:flex;align-items:flex-end;gap:10px}.cv-compose-input{flex:1;min-width:0}.cv-attach-btn,.cv-send-btn{flex-shrink:0}.cv-attachment{display:flex;align-items:center;gap:8px;padding:8px 12px;margin-bottom:8px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px}.cv-attachment-thumb{width:48px;height:48px;object-fit:cover;border-radius:6px}.cv-attachment-name{font-size:.8125rem;color:#475569;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cv-attachment-remove{flex-shrink:0}.cv-compose-status{margin:4px 0 0;font-size:.8125rem;color:#64748b}.cv-compose-status:empty{margin:0}.cv-context-backdrop{display:none}.cv-context{width:100%;min-width:0;border-left:1px solid #e2e8f0;background:#fff;display:flex;flex-direction:column;overflow-y:auto}.cv-context-header{display:flex;justify-content:space-between;align-items:center;padding:20px;border-bottom:1px solid #e2e8f0}.cv-context-header h3{margin:0;font-size:.875rem;font-weight:700;color:#1e293b}.cv-context-body{padding:20px;display:flex;flex-direction:column;gap:16px}@keyframes cv-appear{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes cv-slide-in{0%{transform:translate(100%)}to{transform:translate(0)}}@media(max-width:1100px){.cv-layout.has-context{grid-template-columns:280px 1fr 240px}}@media(max-width:768px){.cv-layout,.cv-layout.has-context{grid-template-columns:1fr;height:100%}.cv-hidden-mobile{display:none!important}.cv-back-btn{display:inline-flex}.cv-context-backdrop{display:block;position:fixed;inset:0;background:#00000059;z-index:99}.cv-context{position:fixed;top:0;right:0;bottom:0;width:300px;max-width:85vw;z-index:100;box-shadow:-4px 0 20px #0000001a}.cv-bubble{max-width:85%}.cv-messages,.cv-thread-header{padding:12px 16px}}@media(max-width:480px){.cv-thread-subtitle{display:none}.cv-compose{padding:8px 12px 12px;padding-bottom:calc(12px + env(safe-area-inset-bottom,0px))}.cv-compose-row{flex-wrap:wrap}.cv-compose-input{order:-1;flex:1 1 100%}.cv-send-btn{margin-inline-start:auto}.cv-list-header{padding:12px 16px 10px}.cv-context{width:100%;max-width:100vw}}\n"]
54414
+ styles: [":host{display:flex;flex:1;min-height:0;min-width:0}.cv-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.cv-layout{display:grid;grid-template-columns:340px 1fr;flex:1;min-height:0;min-width:0;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px #0000000f;border:1px solid #e2e8f0}.cv-layout.has-context{grid-template-columns:340px 1fr 280px}.cv-list-panel{display:flex;flex-direction:column;min-height:0;border-right:1px solid #e2e8f0;background:#f8fafc;overflow:hidden}.cv-list-header{display:flex;align-items:center;justify-content:space-between;padding:20px 20px 12px}.cv-list-title{margin:0;font-size:1.125rem;font-weight:700;color:#1e293b}.cv-search,.cv-filters{padding:0 16px 12px}.cv-list{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}.cv-item{display:flex;gap:12px;padding:14px 16px;border-bottom:1px solid #f1f5f9;transition:background .15s}.cv-item.unread{background:#fffbeb}.cv-item.selected{background:#eff6ff;box-shadow:inset 3px 0 #2563eb}.cv-item-info{flex:1;min-width:0}.cv-row-arrow{flex:0 0 auto;align-self:center;color:#94a3b8}.cv-item-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.cv-name{font-size:.875rem;font-weight:600;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cv-time{font-size:.6875rem;color:#64748b;flex-shrink:0;margin-left:8px}.cv-subtitle{font-size:.6875rem;color:#64748b;display:block;margin-bottom:2px}.cv-item-bottom{display:flex;justify-content:space-between;align-items:center;gap:8px}.cv-preview{margin:0;font-size:.8125rem;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.cv-item-right{display:flex;align-items:center;gap:6px;flex-shrink:0}.cv-delete-btn{opacity:0;transition:opacity .15s}.cv-item:hover .cv-delete-btn,.cv-item:focus-within .cv-delete-btn,.cv-delete-btn:focus-within{opacity:1}@media(hover:none){.cv-delete-btn{opacity:1}}.cv-loading{padding:8px 0}.cv-skeleton{display:flex;gap:12px;align-items:flex-start;padding:14px 16px;border-bottom:1px solid #f1f5f9}.cv-skel-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px;padding-top:2px}.cv-skel-row{display:flex;justify-content:space-between;gap:8px}.cv-error{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 16px;text-align:center}.cv-thread-panel{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden;background:#fff}.cv-thread-header{display:flex;align-items:center;gap:12px;padding:16px 20px;border-bottom:1px solid #e2e8f0;background:#fff}.cv-back-btn{display:none}.cv-thread-header-info{display:flex;align-items:center;gap:12px;flex:1;min-width:0}.cv-thread-header-text{min-width:0;display:flex;flex-direction:column}.cv-thread-name{margin:0;font-size:.9375rem;font-weight:600;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cv-thread-name:focus{outline:none}.cv-thread-name:focus-visible{outline:2px solid #2563eb;outline-offset:2px;border-radius:4px}.cv-thread-subtitle{font-size:.75rem;color:#64748b}.cv-thread-actions{display:flex;align-items:center;gap:8px}.cv-messages{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;padding:20px 24px;display:flex;flex-direction:column;gap:4px;background:#f8fafc}.cv-messages:focus-visible{outline:2px solid #2563eb;outline-offset:-2px}.cv-messages-loading{display:flex;flex-direction:column;gap:12px;padding:20px 0}.cv-msg-skeleton{display:flex}.cv-msg-skeleton.outbound{justify-content:flex-end}.cv-messages-error{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 16px;text-align:center}.cv-load-earlier{display:flex;justify-content:center;padding:4px 0 12px}.cv-sender{font-weight:600}.cv-link-chip{display:inline-flex;align-items:center;gap:6px;max-width:100%;margin-top:6px;padding:3px 10px;border-radius:999px;border:1px solid transparent;font-size:.8rem;font-weight:600;text-decoration:none}.cv-link-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.cv-link-icon{flex:none}.cv-bubble.inbound .cv-link-chip{background:#eff6ff;border-color:#bfdbfe;color:#1d4ed8}.cv-bubble.outbound .cv-link-chip{background:#ffffff2e;border-color:#ffffff73;color:#fff}.cv-link-chip:is(a):hover{filter:brightness(.96);text-decoration:underline}.cv-link-chip:is(a):focus-visible{outline:2px solid currentColor;outline-offset:2px}.cv-link-preview{display:flex;align-items:center;gap:4px;margin-bottom:8px}.cv-link-chip-compose{margin-top:0;background:#eff6ff;border-color:#bfdbfe;color:#1d4ed8}.cv-link-list{display:flex;flex-direction:column;gap:4px;margin-top:12px;max-height:320px;overflow-y:auto}.cv-link-section-label{padding:8px 4px 4px;font-size:.7rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:#64748b}.cv-link-item{display:block;width:100%;padding:10px 12px;border:0;border-radius:8px;background:none;text-align:start;font:inherit;font-size:.875rem;color:#1e293b;cursor:pointer}.cv-link-item:hover{background:#f1f5f9}.cv-link-item:focus-visible{outline:2px solid #2563eb;outline-offset:-2px}.cv-link-note{margin:16px 0;text-align:center;font-size:.875rem;color:#64748b}@media(pointer:coarse),(max-width:480px){.cv-link-item{min-height:44px}}.cv-msg-action{padding:0;border:0;background:none;color:inherit;font:inherit;text-decoration:underline;cursor:pointer}.cv-msg-action:focus-visible{outline:2px solid currentColor;outline-offset:2px;border-radius:2px}.cv-messages-empty{display:flex;align-items:center;justify-content:center;flex:1}.cv-messages-empty p{margin:0;font-size:.875rem;color:#64748b;text-align:center}.cv-date-separator{display:flex;align-items:center;justify-content:center;padding:16px 0 8px}.cv-date-separator span{padding:4px 14px;border-radius:12px;background:#e2e8f0;font-size:.6875rem;font-weight:600;color:#475569;text-transform:uppercase;letter-spacing:.3px}.cv-bubble{max-width:70%;padding:10px 14px;border-radius:16px;position:relative;word-break:break-word;overflow-wrap:break-word}.cv-bubble.inbound{align-self:flex-start;background:#fff;border:1px solid #e2e8f0;border-bottom-left-radius:4px}.cv-bubble.inbound .cv-body,.cv-bubble.inbound .cv-rich{color:#1e293b}.cv-bubble.inbound .cv-meta{color:#64748b}.cv-bubble.outbound{align-self:flex-end;background:#2563eb;border-bottom-right-radius:4px}.cv-bubble.outbound .cv-body,.cv-bubble.outbound .cv-rich,.cv-bubble.outbound .cv-meta{color:#fff}@media(prefers-reduced-motion:no-preference){.cv-bubble{animation:cv-appear .2s ease}.cv-context{animation:cv-slide-in .25s ease}}.cv-body{margin:0;font-size:.875rem;line-height:1.45;word-break:break-word;white-space:pre-wrap}.cv-media{margin-bottom:6px}.cv-image-btn{display:block;padding:0;border:0;background:none;cursor:pointer;border-radius:8px}.cv-image-btn:focus-visible{outline:2px solid #fff;outline-offset:2px;box-shadow:0 0 0 4px #2563eb}.cv-image{display:block;max-width:240px;max-height:240px;border-radius:8px;object-fit:cover}.cv-video{max-width:240px;max-height:200px;border-radius:8px}.cv-file-link{font-size:.8125rem;color:#fff;text-decoration:underline}.cv-bubble.inbound .cv-file-link{color:#2563eb}.cv-meta{display:flex;align-items:center;gap:6px;justify-content:flex-end;font-size:.6875rem;margin-top:4px}.cv-status{font-weight:500}.cv-status.failed{color:#fff;font-weight:700}.cv-bubble.inbound .cv-status.failed{color:#dc2626}.cv-compose{display:flex;flex-direction:column;padding:12px 20px 16px;padding-bottom:calc(16px + env(safe-area-inset-bottom,0px));border-top:1px solid #e2e8f0;background:#fff}.cv-compose-row{display:flex;align-items:flex-end;gap:10px}.cv-compose-input{flex:1;min-width:0}.cv-attach-btn,.cv-send-btn{flex-shrink:0}.cv-attachment{display:flex;align-items:center;gap:8px;padding:8px 12px;margin-bottom:8px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px}.cv-attachment-thumb{width:48px;height:48px;object-fit:cover;border-radius:6px}.cv-attachment-name{font-size:.8125rem;color:#475569;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cv-attachment-remove{flex-shrink:0}.cv-compose-status{margin:4px 0 0;font-size:.8125rem;color:#64748b}.cv-compose-status:empty{margin:0}.cv-context-backdrop{display:none}.cv-context{width:100%;min-width:0;border-left:1px solid #e2e8f0;background:#fff;display:flex;flex-direction:column;overflow-y:auto}.cv-context-header{display:flex;justify-content:space-between;align-items:center;padding:20px;border-bottom:1px solid #e2e8f0}.cv-context-header h3{margin:0;font-size:.875rem;font-weight:700;color:#1e293b}.cv-context-body{padding:20px;display:flex;flex-direction:column;gap:16px}@keyframes cv-appear{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes cv-slide-in{0%{transform:translate(100%)}to{transform:translate(0)}}@media(max-width:1100px){.cv-layout.has-context{grid-template-columns:280px 1fr 240px}}@media(max-width:768px){.cv-layout,.cv-layout.has-context{grid-template-columns:1fr;height:100%}.cv-hidden-mobile{display:none!important}.cv-back-btn{display:inline-flex}.cv-context-backdrop{display:block;position:fixed;inset:0;background:#00000059;z-index:99}.cv-context{position:fixed;top:0;right:0;bottom:0;width:300px;max-width:85vw;z-index:100;box-shadow:-4px 0 20px #0000001a}.cv-bubble{max-width:85%}.cv-messages,.cv-thread-header{padding:12px 16px}}@media(max-width:480px){.cv-thread-subtitle{display:none}.cv-compose{padding:8px 12px 12px;padding-bottom:calc(12px + env(safe-area-inset-bottom,0px))}.cv-compose-row{flex-wrap:wrap}.cv-compose-input{order:-1;flex:1 1 100%}.cv-send-btn{margin-inline-start:auto}.cv-list-header{padding:12px 16px 10px}.cv-context{width:100%;max-width:100vw}}\n"]
53864
54415
  }]
53865
54416
  }],
53866
54417
  ctorParameters: () => [],
@@ -54606,6 +55157,93 @@ i0.ɵɵngDeclareClassMetadata({
54606
55157
  type: i0.Output,
54607
55158
  args: ["attachmentRemove"]
54608
55159
  }],
55160
+ linkGroups: [{
55161
+ type: i0.Input,
55162
+ args: [{
55163
+ isSignal: true,
55164
+ alias: "linkGroups",
55165
+ required: false
55166
+ }]
55167
+ }],
55168
+ linkLoading: [{
55169
+ type: i0.Input,
55170
+ args: [{
55171
+ isSignal: true,
55172
+ alias: "linkLoading",
55173
+ required: false
55174
+ }]
55175
+ }],
55176
+ link: [{
55177
+ type: i0.Input,
55178
+ args: [{
55179
+ isSignal: true,
55180
+ alias: "link",
55181
+ required: false
55182
+ }]
55183
+ }, {
55184
+ type: i0.Output,
55185
+ args: ["linkChange"]
55186
+ }],
55187
+ linkLabel: [{
55188
+ type: i0.Input,
55189
+ args: [{
55190
+ isSignal: true,
55191
+ alias: "linkLabel",
55192
+ required: false
55193
+ }]
55194
+ }],
55195
+ linkPickerTitle: [{
55196
+ type: i0.Input,
55197
+ args: [{
55198
+ isSignal: true,
55199
+ alias: "linkPickerTitle",
55200
+ required: false
55201
+ }]
55202
+ }],
55203
+ linkSearchPlaceholder: [{
55204
+ type: i0.Input,
55205
+ args: [{
55206
+ isSignal: true,
55207
+ alias: "linkSearchPlaceholder",
55208
+ required: false
55209
+ }]
55210
+ }],
55211
+ linkSearchLabel: [{
55212
+ type: i0.Input,
55213
+ args: [{
55214
+ isSignal: true,
55215
+ alias: "linkSearchLabel",
55216
+ required: false
55217
+ }]
55218
+ }],
55219
+ linkEmptyText: [{
55220
+ type: i0.Input,
55221
+ args: [{
55222
+ isSignal: true,
55223
+ alias: "linkEmptyText",
55224
+ required: false
55225
+ }]
55226
+ }],
55227
+ linkLoadingText: [{
55228
+ type: i0.Input,
55229
+ args: [{
55230
+ isSignal: true,
55231
+ alias: "linkLoadingText",
55232
+ required: false
55233
+ }]
55234
+ }],
55235
+ removeLinkLabel: [{
55236
+ type: i0.Input,
55237
+ args: [{
55238
+ isSignal: true,
55239
+ alias: "removeLinkLabel",
55240
+ required: false
55241
+ }]
55242
+ }],
55243
+ linkPickerOpened: [{
55244
+ type: i0.Output,
55245
+ args: ["linkPickerOpened"]
55246
+ }],
54609
55247
  mediaOpen: [{
54610
55248
  type: i0.Output,
54611
55249
  args: ["mediaOpen"]