@eric-emg/symphiq-components 1.2.35 → 1.2.36

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.
@@ -5,7 +5,7 @@ import { Injectable, signal, input, ChangeDetectionStrategy, Component, output,
5
5
  import { BehaviorSubject } from 'rxjs';
6
6
  import * as i1 from '@angular/common';
7
7
  import { NgClass, CommonModule } from '@angular/common';
8
- import * as i5 from '@angular/forms';
8
+ import * as i6 from '@angular/forms';
9
9
  import { FormsModule } from '@angular/forms';
10
10
  import { DomSanitizer } from '@angular/platform-browser';
11
11
  import { trigger, transition, style, animate } from '@angular/animations';
@@ -163,23 +163,45 @@ class TooltipService {
163
163
  content: null,
164
164
  position: 'top',
165
165
  targetRect: null,
166
- visible: false
166
+ visible: false,
167
+ scrollContainer: null,
168
+ checkHideCallback: undefined
167
169
  });
168
170
  this.tooltipState$ = this.tooltipState.asObservable();
171
+ this.scrollContainer = null;
172
+ this.isTooltipHovered = false;
169
173
  }
170
- show(type, content, targetRect, position = 'top', mousePosition) {
174
+ setScrollContainer(container) {
175
+ this.scrollContainer = container;
176
+ }
177
+ setTooltipHovered(hovered) {
178
+ this.isTooltipHovered = hovered;
179
+ }
180
+ isHoveredOverTooltip() {
181
+ return this.isTooltipHovered;
182
+ }
183
+ triggerHideCheck() {
184
+ const callback = this.tooltipState.value.checkHideCallback;
185
+ if (callback) {
186
+ callback();
187
+ }
188
+ }
189
+ show(type, content, targetRect, position = 'top', mousePosition, checkHideCallback) {
171
190
  // Auto-adjust position if too close to screen edges
172
191
  let adjustedPosition = position;
173
192
  // Use different thresholds based on tooltip type
174
193
  // Insight tooltips can be quite tall, so need more space
175
194
  const HORIZONTAL_THRESHOLD = 200;
176
195
  const VERTICAL_THRESHOLD = type === 'insight' ? 350 : 200;
196
+ // Get viewport dimensions - use scroll container if available, otherwise window
197
+ const viewportWidth = this.scrollContainer?.clientWidth || window.innerWidth;
198
+ const viewportHeight = this.scrollContainer?.clientHeight || window.innerHeight;
177
199
  // Check left edge
178
200
  if (position === 'left' && targetRect.left < HORIZONTAL_THRESHOLD) {
179
201
  adjustedPosition = 'right';
180
202
  }
181
203
  // Check right edge
182
- if (position === 'right' && (window.innerWidth - targetRect.right) < HORIZONTAL_THRESHOLD) {
204
+ if (position === 'right' && (viewportWidth - targetRect.right) < HORIZONTAL_THRESHOLD) {
183
205
  adjustedPosition = 'left';
184
206
  }
185
207
  // Check top edge
@@ -187,12 +209,12 @@ class TooltipService {
187
209
  adjustedPosition = 'bottom';
188
210
  }
189
211
  // Check bottom edge - prioritize flipping to top if not enough space
190
- if (position === 'bottom' && (window.innerHeight - targetRect.bottom) < VERTICAL_THRESHOLD) {
212
+ if (position === 'bottom' && (viewportHeight - targetRect.bottom) < VERTICAL_THRESHOLD) {
191
213
  adjustedPosition = 'top';
192
214
  }
193
215
  // Smart auto-detection for 'top' position tooltips near bottom
194
216
  if (position === 'top') {
195
- const spaceBelow = window.innerHeight - targetRect.bottom;
217
+ const spaceBelow = viewportHeight - targetRect.bottom;
196
218
  const spaceAbove = targetRect.top;
197
219
  // If more space above and not enough space below, keep it on top
198
220
  // If more space below, switch to bottom
@@ -203,15 +225,27 @@ class TooltipService {
203
225
  adjustedPosition = 'bottom';
204
226
  }
205
227
  }
206
- this.tooltipState.next({ type, content, position: adjustedPosition, targetRect, mousePosition, visible: true });
228
+ this.tooltipState.next({
229
+ type,
230
+ content,
231
+ position: adjustedPosition,
232
+ targetRect,
233
+ mousePosition,
234
+ visible: true,
235
+ scrollContainer: this.scrollContainer,
236
+ checkHideCallback
237
+ });
207
238
  }
208
239
  hide() {
240
+ this.isTooltipHovered = false;
209
241
  this.tooltipState.next({
210
242
  type: null,
211
243
  content: null,
212
244
  position: 'top',
213
245
  targetRect: null,
214
- visible: false
246
+ visible: false,
247
+ scrollContainer: this.scrollContainer,
248
+ checkHideCallback: undefined
215
249
  });
216
250
  }
217
251
  updatePosition(targetRect) {
@@ -1456,6 +1490,11 @@ class TooltipDirective {
1456
1490
  this.isHovered.set(false);
1457
1491
  this.scheduleHide();
1458
1492
  }
1493
+ onMouseEnterAgain() {
1494
+ // If user returns to element while tooltip is still visible, cancel hide
1495
+ this.clearHideTimeout();
1496
+ this.isHovered.set(true);
1497
+ }
1459
1498
  onFocus() {
1460
1499
  this.clearHideTimeout();
1461
1500
  this.isHovered.set(true);
@@ -1475,14 +1514,17 @@ class TooltipDirective {
1475
1514
  const position = this.tooltipPosition();
1476
1515
  if (content && this.isHovered()) {
1477
1516
  const mousePos = position === 'auto' ? { x: this.mouseX, y: this.mouseY } : undefined;
1478
- this.tooltipService.show(type, content, rect, position, mousePos);
1517
+ // Pass a callback that the tooltip can use to trigger hide checks
1518
+ const checkHideCallback = () => this.scheduleHide();
1519
+ this.tooltipService.show(type, content, rect, position, mousePos, checkHideCallback);
1479
1520
  }
1480
1521
  }, this.tooltipDelay());
1481
1522
  }
1482
1523
  scheduleHide() {
1483
1524
  this.clearHideTimeout();
1484
1525
  this.hideTimeout = setTimeout(() => {
1485
- if (!this.isHovered()) {
1526
+ // Only hide if neither the element nor the tooltip is hovered
1527
+ if (!this.isHovered() && !this.tooltipService.isHoveredOverTooltip()) {
1486
1528
  this.tooltipService.hide();
1487
1529
  }
1488
1530
  }, 100);
@@ -1506,7 +1548,7 @@ class TooltipDirective {
1506
1548
  }
1507
1549
  static { this.ɵfac = function TooltipDirective_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || TooltipDirective)(); }; }
1508
1550
  static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: TooltipDirective, selectors: [["", "libSymphiqTooltip", ""]], hostBindings: function TooltipDirective_HostBindings(rf, ctx) { if (rf & 1) {
1509
- i0.ɵɵlistener("mouseenter", function TooltipDirective_mouseenter_HostBindingHandler($event) { return ctx.onMouseEnter($event); })("mousemove", function TooltipDirective_mousemove_HostBindingHandler($event) { return ctx.onMouseMove($event); })("mouseleave", function TooltipDirective_mouseleave_HostBindingHandler() { return ctx.onMouseLeave(); })("focus", function TooltipDirective_focus_HostBindingHandler() { return ctx.onFocus(); })("blur", function TooltipDirective_blur_HostBindingHandler() { return ctx.onBlur(); });
1551
+ i0.ɵɵlistener("mouseenter", function TooltipDirective_mouseenter_HostBindingHandler() { return ctx.onMouseEnterAgain(); })("mousemove", function TooltipDirective_mousemove_HostBindingHandler($event) { return ctx.onMouseMove($event); })("mouseleave", function TooltipDirective_mouseleave_HostBindingHandler() { return ctx.onMouseLeave(); })("focus", function TooltipDirective_focus_HostBindingHandler() { return ctx.onFocus(); })("blur", function TooltipDirective_blur_HostBindingHandler() { return ctx.onBlur(); });
1510
1552
  } }, inputs: { symphiqTooltip: [1, "libSymphiqTooltip", "symphiqTooltip"], tooltipType: [1, "tooltipType"], tooltipPosition: [1, "tooltipPosition"], tooltipDelay: [1, "tooltipDelay"] } }); }
1511
1553
  }
1512
1554
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TooltipDirective, [{
@@ -1524,6 +1566,9 @@ class TooltipDirective {
1524
1566
  }], onMouseLeave: [{
1525
1567
  type: HostListener,
1526
1568
  args: ['mouseleave']
1569
+ }], onMouseEnterAgain: [{
1570
+ type: HostListener,
1571
+ args: ['mouseenter']
1527
1572
  }], onFocus: [{
1528
1573
  type: HostListener,
1529
1574
  args: ['focus']
@@ -10778,110 +10823,110 @@ const _c0$6 = () => [];
10778
10823
  function TooltipContainerComponent_Conditional_0_Case_3_Conditional_0_Template(rf, ctx) { if (rf & 1) {
10779
10824
  i0.ɵɵelement(0, "symphiq-metric-value-tooltip", 6);
10780
10825
  } if (rf & 2) {
10781
- const ctx_r0 = i0.ɵɵnextContext(3);
10782
- i0.ɵɵproperty("content", ctx_r0.metricContent())("isLightMode", ctx_r0.isLightMode());
10826
+ const ctx_r1 = i0.ɵɵnextContext(3);
10827
+ i0.ɵɵproperty("content", ctx_r1.metricContent())("isLightMode", ctx_r1.isLightMode());
10783
10828
  } }
10784
10829
  function TooltipContainerComponent_Conditional_0_Case_3_Template(rf, ctx) { if (rf & 1) {
10785
10830
  i0.ɵɵconditionalCreate(0, TooltipContainerComponent_Conditional_0_Case_3_Conditional_0_Template, 1, 2, "symphiq-metric-value-tooltip", 6);
10786
10831
  } if (rf & 2) {
10787
- const ctx_r0 = i0.ɵɵnextContext(2);
10788
- i0.ɵɵconditional(ctx_r0.metricContent() ? 0 : -1);
10832
+ const ctx_r1 = i0.ɵɵnextContext(2);
10833
+ i0.ɵɵconditional(ctx_r1.metricContent() ? 0 : -1);
10789
10834
  } }
10790
10835
  function TooltipContainerComponent_Conditional_0_Case_4_Conditional_0_Template(rf, ctx) { if (rf & 1) {
10791
10836
  i0.ɵɵelement(0, "symphiq-status-badge-tooltip", 6);
10792
10837
  } if (rf & 2) {
10793
- const ctx_r0 = i0.ɵɵnextContext(3);
10794
- i0.ɵɵproperty("content", ctx_r0.statusContent())("isLightMode", ctx_r0.isLightMode());
10838
+ const ctx_r1 = i0.ɵɵnextContext(3);
10839
+ i0.ɵɵproperty("content", ctx_r1.statusContent())("isLightMode", ctx_r1.isLightMode());
10795
10840
  } }
10796
10841
  function TooltipContainerComponent_Conditional_0_Case_4_Template(rf, ctx) { if (rf & 1) {
10797
10842
  i0.ɵɵconditionalCreate(0, TooltipContainerComponent_Conditional_0_Case_4_Conditional_0_Template, 1, 2, "symphiq-status-badge-tooltip", 6);
10798
10843
  } if (rf & 2) {
10799
- const ctx_r0 = i0.ɵɵnextContext(2);
10800
- i0.ɵɵconditional(ctx_r0.statusContent() ? 0 : -1);
10844
+ const ctx_r1 = i0.ɵɵnextContext(2);
10845
+ i0.ɵɵconditional(ctx_r1.statusContent() ? 0 : -1);
10801
10846
  } }
10802
10847
  function TooltipContainerComponent_Conditional_0_Case_5_Conditional_0_Template(rf, ctx) { if (rf & 1) {
10803
10848
  i0.ɵɵelement(0, "symphiq-trend-indicator-tooltip", 6);
10804
10849
  } if (rf & 2) {
10805
- const ctx_r0 = i0.ɵɵnextContext(3);
10806
- i0.ɵɵproperty("content", ctx_r0.trendContent())("isLightMode", ctx_r0.isLightMode());
10850
+ const ctx_r1 = i0.ɵɵnextContext(3);
10851
+ i0.ɵɵproperty("content", ctx_r1.trendContent())("isLightMode", ctx_r1.isLightMode());
10807
10852
  } }
10808
10853
  function TooltipContainerComponent_Conditional_0_Case_5_Template(rf, ctx) { if (rf & 1) {
10809
10854
  i0.ɵɵconditionalCreate(0, TooltipContainerComponent_Conditional_0_Case_5_Conditional_0_Template, 1, 2, "symphiq-trend-indicator-tooltip", 6);
10810
10855
  } if (rf & 2) {
10811
- const ctx_r0 = i0.ɵɵnextContext(2);
10812
- i0.ɵɵconditional(ctx_r0.trendContent() ? 0 : -1);
10856
+ const ctx_r1 = i0.ɵɵnextContext(2);
10857
+ i0.ɵɵconditional(ctx_r1.trendContent() ? 0 : -1);
10813
10858
  } }
10814
10859
  function TooltipContainerComponent_Conditional_0_Case_6_Conditional_0_Template(rf, ctx) { if (rf & 1) {
10815
10860
  i0.ɵɵelement(0, "symphiq-priority-badge-tooltip", 6);
10816
10861
  } if (rf & 2) {
10817
- const ctx_r0 = i0.ɵɵnextContext(3);
10818
- i0.ɵɵproperty("content", ctx_r0.priorityContent())("isLightMode", ctx_r0.isLightMode());
10862
+ const ctx_r1 = i0.ɵɵnextContext(3);
10863
+ i0.ɵɵproperty("content", ctx_r1.priorityContent())("isLightMode", ctx_r1.isLightMode());
10819
10864
  } }
10820
10865
  function TooltipContainerComponent_Conditional_0_Case_6_Template(rf, ctx) { if (rf & 1) {
10821
10866
  i0.ɵɵconditionalCreate(0, TooltipContainerComponent_Conditional_0_Case_6_Conditional_0_Template, 1, 2, "symphiq-priority-badge-tooltip", 6);
10822
10867
  } if (rf & 2) {
10823
- const ctx_r0 = i0.ɵɵnextContext(2);
10824
- i0.ɵɵconditional(ctx_r0.priorityContent() ? 0 : -1);
10868
+ const ctx_r1 = i0.ɵɵnextContext(2);
10869
+ i0.ɵɵconditional(ctx_r1.priorityContent() ? 0 : -1);
10825
10870
  } }
10826
10871
  function TooltipContainerComponent_Conditional_0_Case_7_Conditional_0_Template(rf, ctx) { if (rf & 1) {
10827
10872
  i0.ɵɵelement(0, "symphiq-badge-tooltip", 6);
10828
10873
  } if (rf & 2) {
10829
- const ctx_r0 = i0.ɵɵnextContext(3);
10830
- i0.ɵɵproperty("content", ctx_r0.badgeContent())("isLightMode", ctx_r0.isLightMode());
10874
+ const ctx_r1 = i0.ɵɵnextContext(3);
10875
+ i0.ɵɵproperty("content", ctx_r1.badgeContent())("isLightMode", ctx_r1.isLightMode());
10831
10876
  } }
10832
10877
  function TooltipContainerComponent_Conditional_0_Case_7_Template(rf, ctx) { if (rf & 1) {
10833
10878
  i0.ɵɵconditionalCreate(0, TooltipContainerComponent_Conditional_0_Case_7_Conditional_0_Template, 1, 2, "symphiq-badge-tooltip", 6);
10834
10879
  } if (rf & 2) {
10835
- const ctx_r0 = i0.ɵɵnextContext(2);
10836
- i0.ɵɵconditional(ctx_r0.badgeContent() ? 0 : -1);
10880
+ const ctx_r1 = i0.ɵɵnextContext(2);
10881
+ i0.ɵɵconditional(ctx_r1.badgeContent() ? 0 : -1);
10837
10882
  } }
10838
10883
  function TooltipContainerComponent_Conditional_0_Case_8_Conditional_0_Template(rf, ctx) { if (rf & 1) {
10839
10884
  i0.ɵɵelement(0, "symphiq-breakdown-row-tooltip", 6);
10840
10885
  } if (rf & 2) {
10841
- const ctx_r0 = i0.ɵɵnextContext(3);
10842
- i0.ɵɵproperty("content", ctx_r0.breakdownContent())("isLightMode", ctx_r0.isLightMode());
10886
+ const ctx_r1 = i0.ɵɵnextContext(3);
10887
+ i0.ɵɵproperty("content", ctx_r1.breakdownContent())("isLightMode", ctx_r1.isLightMode());
10843
10888
  } }
10844
10889
  function TooltipContainerComponent_Conditional_0_Case_8_Template(rf, ctx) { if (rf & 1) {
10845
10890
  i0.ɵɵconditionalCreate(0, TooltipContainerComponent_Conditional_0_Case_8_Conditional_0_Template, 1, 2, "symphiq-breakdown-row-tooltip", 6);
10846
10891
  } if (rf & 2) {
10847
- const ctx_r0 = i0.ɵɵnextContext(2);
10848
- i0.ɵɵconditional(ctx_r0.breakdownContent() ? 0 : -1);
10892
+ const ctx_r1 = i0.ɵɵnextContext(2);
10893
+ i0.ɵɵconditional(ctx_r1.breakdownContent() ? 0 : -1);
10849
10894
  } }
10850
10895
  function TooltipContainerComponent_Conditional_0_Case_9_Conditional_0_Template(rf, ctx) { if (rf & 1) {
10851
10896
  i0.ɵɵelement(0, "symphiq-competitive-benchmark-tooltip", 6);
10852
10897
  } if (rf & 2) {
10853
- const ctx_r0 = i0.ɵɵnextContext(3);
10854
- i0.ɵɵproperty("content", ctx_r0.competitiveContent())("isLightMode", ctx_r0.isLightMode());
10898
+ const ctx_r1 = i0.ɵɵnextContext(3);
10899
+ i0.ɵɵproperty("content", ctx_r1.competitiveContent())("isLightMode", ctx_r1.isLightMode());
10855
10900
  } }
10856
10901
  function TooltipContainerComponent_Conditional_0_Case_9_Template(rf, ctx) { if (rf & 1) {
10857
10902
  i0.ɵɵconditionalCreate(0, TooltipContainerComponent_Conditional_0_Case_9_Conditional_0_Template, 1, 2, "symphiq-competitive-benchmark-tooltip", 6);
10858
10903
  } if (rf & 2) {
10859
- const ctx_r0 = i0.ɵɵnextContext(2);
10860
- i0.ɵɵconditional(ctx_r0.competitiveContent() ? 0 : -1);
10904
+ const ctx_r1 = i0.ɵɵnextContext(2);
10905
+ i0.ɵɵconditional(ctx_r1.competitiveContent() ? 0 : -1);
10861
10906
  } }
10862
10907
  function TooltipContainerComponent_Conditional_0_Case_10_Conditional_0_Template(rf, ctx) { if (rf & 1) {
10863
10908
  i0.ɵɵelement(0, "symphiq-insights-list-tooltip", 6);
10864
10909
  } if (rf & 2) {
10865
- const ctx_r0 = i0.ɵɵnextContext(3);
10866
- i0.ɵɵproperty("content", ctx_r0.insightsListContent())("isLightMode", ctx_r0.isLightMode());
10910
+ const ctx_r1 = i0.ɵɵnextContext(3);
10911
+ i0.ɵɵproperty("content", ctx_r1.insightsListContent())("isLightMode", ctx_r1.isLightMode());
10867
10912
  } }
10868
10913
  function TooltipContainerComponent_Conditional_0_Case_10_Template(rf, ctx) { if (rf & 1) {
10869
10914
  i0.ɵɵconditionalCreate(0, TooltipContainerComponent_Conditional_0_Case_10_Conditional_0_Template, 1, 2, "symphiq-insights-list-tooltip", 6);
10870
10915
  } if (rf & 2) {
10871
- const ctx_r0 = i0.ɵɵnextContext(2);
10872
- i0.ɵɵconditional(ctx_r0.insightsListContent() ? 0 : -1);
10916
+ const ctx_r1 = i0.ɵɵnextContext(2);
10917
+ i0.ɵɵconditional(ctx_r1.insightsListContent() ? 0 : -1);
10873
10918
  } }
10874
10919
  function TooltipContainerComponent_Conditional_0_Case_11_Conditional_0_Template(rf, ctx) { if (rf & 1) {
10875
10920
  i0.ɵɵelement(0, "symphiq-narrative-tooltip", 6);
10876
10921
  } if (rf & 2) {
10877
- const ctx_r0 = i0.ɵɵnextContext(3);
10878
- i0.ɵɵproperty("content", ctx_r0.narrativeContent())("isLightMode", ctx_r0.isLightMode());
10922
+ const ctx_r1 = i0.ɵɵnextContext(3);
10923
+ i0.ɵɵproperty("content", ctx_r1.narrativeContent())("isLightMode", ctx_r1.isLightMode());
10879
10924
  } }
10880
10925
  function TooltipContainerComponent_Conditional_0_Case_11_Template(rf, ctx) { if (rf & 1) {
10881
10926
  i0.ɵɵconditionalCreate(0, TooltipContainerComponent_Conditional_0_Case_11_Conditional_0_Template, 1, 2, "symphiq-narrative-tooltip", 6);
10882
10927
  } if (rf & 2) {
10883
- const ctx_r0 = i0.ɵɵnextContext(2);
10884
- i0.ɵɵconditional(ctx_r0.narrativeContent() ? 0 : -1);
10928
+ const ctx_r1 = i0.ɵɵnextContext(2);
10929
+ i0.ɵɵconditional(ctx_r1.narrativeContent() ? 0 : -1);
10885
10930
  } }
10886
10931
  function TooltipContainerComponent_Conditional_0_Case_12_Conditional_1_For_1_Template(rf, ctx) { if (rf & 1) {
10887
10932
  i0.ɵɵelementStart(0, "div", 7)(1, "div", 8);
@@ -10891,18 +10936,18 @@ function TooltipContainerComponent_Conditional_0_Case_12_Conditional_1_For_1_Tem
10891
10936
  i0.ɵɵtext(4);
10892
10937
  i0.ɵɵelementEnd()();
10893
10938
  } if (rf & 2) {
10894
- const section_r2 = ctx.$implicit;
10939
+ const section_r3 = ctx.$implicit;
10895
10940
  i0.ɵɵadvance(2);
10896
- i0.ɵɵtextInterpolate(section_r2.title);
10941
+ i0.ɵɵtextInterpolate(section_r3.title);
10897
10942
  i0.ɵɵadvance(2);
10898
- i0.ɵɵtextInterpolate(section_r2.content);
10943
+ i0.ɵɵtextInterpolate(section_r3.content);
10899
10944
  } }
10900
10945
  function TooltipContainerComponent_Conditional_0_Case_12_Conditional_1_Template(rf, ctx) { if (rf & 1) {
10901
10946
  i0.ɵɵrepeaterCreate(0, TooltipContainerComponent_Conditional_0_Case_12_Conditional_1_For_1_Template, 5, 2, "div", 7, i0.ɵɵrepeaterTrackByIndex);
10902
10947
  } if (rf & 2) {
10903
10948
  i0.ɵɵnextContext(2);
10904
- const content_r3 = i0.ɵɵreadContextLet(2);
10905
- i0.ɵɵrepeater(content_r3["sections"] || i0.ɵɵpureFunction0(0, _c0$6));
10949
+ const content_r4 = i0.ɵɵreadContextLet(2);
10950
+ i0.ɵɵrepeater(content_r4["sections"] || i0.ɵɵpureFunction0(0, _c0$6));
10906
10951
  } }
10907
10952
  function TooltipContainerComponent_Conditional_0_Case_12_Template(rf, ctx) { if (rf & 1) {
10908
10953
  i0.ɵɵelementStart(0, "div", 3);
@@ -10910,25 +10955,25 @@ function TooltipContainerComponent_Conditional_0_Case_12_Template(rf, ctx) { if
10910
10955
  i0.ɵɵelementEnd();
10911
10956
  } if (rf & 2) {
10912
10957
  i0.ɵɵnextContext();
10913
- const content_r3 = i0.ɵɵreadContextLet(2);
10914
- const ctx_r0 = i0.ɵɵnextContext();
10915
- i0.ɵɵproperty("ngClass", ctx_r0.textClass());
10958
+ const content_r4 = i0.ɵɵreadContextLet(2);
10959
+ const ctx_r1 = i0.ɵɵnextContext();
10960
+ i0.ɵɵproperty("ngClass", ctx_r1.textClass());
10916
10961
  i0.ɵɵadvance();
10917
- i0.ɵɵconditional(content_r3 && typeof content_r3 === "object" && "sections" in content_r3 ? 1 : -1);
10962
+ i0.ɵɵconditional(content_r4 && typeof content_r4 === "object" && "sections" in content_r4 ? 1 : -1);
10918
10963
  } }
10919
10964
  function TooltipContainerComponent_Conditional_0_Case_13_Conditional_1_Template(rf, ctx) { if (rf & 1) {
10920
10965
  i0.ɵɵtext(0);
10921
10966
  } if (rf & 2) {
10922
10967
  i0.ɵɵnextContext(2);
10923
- const content_r3 = i0.ɵɵreadContextLet(2);
10924
- i0.ɵɵtextInterpolate1(" ", content_r3["text"], " ");
10968
+ const content_r4 = i0.ɵɵreadContextLet(2);
10969
+ i0.ɵɵtextInterpolate1(" ", content_r4["text"], " ");
10925
10970
  } }
10926
10971
  function TooltipContainerComponent_Conditional_0_Case_13_Conditional_2_Template(rf, ctx) { if (rf & 1) {
10927
10972
  i0.ɵɵtext(0);
10928
10973
  } if (rf & 2) {
10929
10974
  i0.ɵɵnextContext(2);
10930
- const content_r3 = i0.ɵɵreadContextLet(2);
10931
- i0.ɵɵtextInterpolate1(" ", content_r3, " ");
10975
+ const content_r4 = i0.ɵɵreadContextLet(2);
10976
+ i0.ɵɵtextInterpolate1(" ", content_r4, " ");
10932
10977
  } }
10933
10978
  function TooltipContainerComponent_Conditional_0_Case_13_Template(rf, ctx) { if (rf & 1) {
10934
10979
  i0.ɵɵelementStart(0, "div", 4);
@@ -10936,11 +10981,11 @@ function TooltipContainerComponent_Conditional_0_Case_13_Template(rf, ctx) { if
10936
10981
  i0.ɵɵelementEnd();
10937
10982
  } if (rf & 2) {
10938
10983
  i0.ɵɵnextContext();
10939
- const content_r3 = i0.ɵɵreadContextLet(2);
10940
- const ctx_r0 = i0.ɵɵnextContext();
10941
- i0.ɵɵproperty("ngClass", ctx_r0.textClass());
10984
+ const content_r4 = i0.ɵɵreadContextLet(2);
10985
+ const ctx_r1 = i0.ɵɵnextContext();
10986
+ i0.ɵɵproperty("ngClass", ctx_r1.textClass());
10942
10987
  i0.ɵɵadvance();
10943
- i0.ɵɵconditional(content_r3 && typeof content_r3 === "object" && "text" in content_r3 ? 1 : 2);
10988
+ i0.ɵɵconditional(content_r4 && typeof content_r4 === "object" && "text" in content_r4 ? 1 : 2);
10944
10989
  } }
10945
10990
  function TooltipContainerComponent_Conditional_0_Case_14_Template(rf, ctx) { if (rf & 1) {
10946
10991
  i0.ɵɵelementStart(0, "div", 5);
@@ -10948,28 +10993,30 @@ function TooltipContainerComponent_Conditional_0_Case_14_Template(rf, ctx) { if
10948
10993
  i0.ɵɵelementEnd();
10949
10994
  } if (rf & 2) {
10950
10995
  i0.ɵɵnextContext();
10951
- const content_r3 = i0.ɵɵreadContextLet(2);
10952
- const ctx_r0 = i0.ɵɵnextContext();
10953
- i0.ɵɵproperty("ngClass", ctx_r0.textClass());
10996
+ const content_r4 = i0.ɵɵreadContextLet(2);
10997
+ const ctx_r1 = i0.ɵɵnextContext();
10998
+ i0.ɵɵproperty("ngClass", ctx_r1.textClass());
10954
10999
  i0.ɵɵadvance();
10955
- i0.ɵɵtextInterpolate1(" ", content_r3, " ");
11000
+ i0.ɵɵtextInterpolate1(" ", content_r4, " ");
10956
11001
  } }
10957
11002
  function TooltipContainerComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
11003
+ const _r1 = i0.ɵɵgetCurrentView();
10958
11004
  i0.ɵɵelementStart(0, "div", 1)(1, "div", 2);
11005
+ i0.ɵɵlistener("mouseenter", function TooltipContainerComponent_Conditional_0_Template_div_mouseenter_1_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onTooltipMouseEnter()); })("mouseleave", function TooltipContainerComponent_Conditional_0_Template_div_mouseleave_1_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onTooltipMouseLeave()); });
10959
11006
  i0.ɵɵdeclareLet(2);
10960
11007
  i0.ɵɵconditionalCreate(3, TooltipContainerComponent_Conditional_0_Case_3_Template, 1, 1)(4, TooltipContainerComponent_Conditional_0_Case_4_Template, 1, 1)(5, TooltipContainerComponent_Conditional_0_Case_5_Template, 1, 1)(6, TooltipContainerComponent_Conditional_0_Case_6_Template, 1, 1)(7, TooltipContainerComponent_Conditional_0_Case_7_Template, 1, 1)(8, TooltipContainerComponent_Conditional_0_Case_8_Template, 1, 1)(9, TooltipContainerComponent_Conditional_0_Case_9_Template, 1, 1)(10, TooltipContainerComponent_Conditional_0_Case_10_Template, 1, 1)(11, TooltipContainerComponent_Conditional_0_Case_11_Template, 1, 1)(12, TooltipContainerComponent_Conditional_0_Case_12_Template, 2, 2, "div", 3)(13, TooltipContainerComponent_Conditional_0_Case_13_Template, 3, 2, "div", 4)(14, TooltipContainerComponent_Conditional_0_Case_14_Template, 2, 2, "div", 5);
10961
11008
  i0.ɵɵelementEnd()();
10962
11009
  } if (rf & 2) {
10963
11010
  let tmp_7_0;
10964
- const ctx_r0 = i0.ɵɵnextContext();
10965
- i0.ɵɵstyleProp("left", ctx_r0.tooltipLeft(), "px")("top", ctx_r0.tooltipTop(), "px");
10966
- i0.ɵɵproperty("ngClass", ctx_r0.containerClass())("@fadeInScale", undefined);
11011
+ const ctx_r1 = i0.ɵɵnextContext();
11012
+ i0.ɵɵstyleProp("left", ctx_r1.tooltipLeft(), "px")("top", ctx_r1.tooltipTop(), "px");
11013
+ i0.ɵɵproperty("ngClass", ctx_r1.containerClass())("@fadeInScale", undefined);
10967
11014
  i0.ɵɵadvance();
10968
- i0.ɵɵproperty("ngClass", ctx_r0.contentClass());
11015
+ i0.ɵɵproperty("ngClass", ctx_r1.contentClass());
10969
11016
  i0.ɵɵadvance();
10970
- i0.ɵɵstoreLet(ctx_r0.tooltipContent());
11017
+ i0.ɵɵstoreLet(ctx_r1.tooltipContent());
10971
11018
  i0.ɵɵadvance();
10972
- i0.ɵɵconditional((tmp_7_0 = ctx_r0.tooltipType()) === "metric" ? 3 : tmp_7_0 === "status" ? 4 : tmp_7_0 === "trend" ? 5 : tmp_7_0 === "priority" ? 6 : tmp_7_0 === "badge" ? 7 : tmp_7_0 === "breakdown" ? 8 : tmp_7_0 === "competitive" ? 9 : tmp_7_0 === "insightsList" ? 10 : tmp_7_0 === "narrative" ? 11 : tmp_7_0 === "insight" ? 12 : tmp_7_0 === "text" ? 13 : 14);
11019
+ i0.ɵɵconditional((tmp_7_0 = ctx_r1.tooltipType()) === "metric" ? 3 : tmp_7_0 === "status" ? 4 : tmp_7_0 === "trend" ? 5 : tmp_7_0 === "priority" ? 6 : tmp_7_0 === "badge" ? 7 : tmp_7_0 === "breakdown" ? 8 : tmp_7_0 === "competitive" ? 9 : tmp_7_0 === "insightsList" ? 10 : tmp_7_0 === "narrative" ? 11 : tmp_7_0 === "insight" ? 12 : tmp_7_0 === "text" ? 13 : 14);
10973
11020
  } }
10974
11021
  class TooltipContainerComponent {
10975
11022
  constructor() {
@@ -10983,7 +11030,8 @@ class TooltipContainerComponent {
10983
11030
  position: 'top',
10984
11031
  targetRect: null,
10985
11032
  mousePosition: undefined,
10986
- visible: false
11033
+ visible: false,
11034
+ scrollContainer: null
10987
11035
  }, ...(ngDevMode ? [{ debugName: "tooltipStateSignal" }] : []));
10988
11036
  this.isVisible = computed(() => this.tooltipStateSignal().visible, ...(ngDevMode ? [{ debugName: "isVisible" }] : []));
10989
11037
  this.tooltipType = computed(() => this.tooltipStateSignal().type, ...(ngDevMode ? [{ debugName: "tooltipType" }] : []));
@@ -10991,6 +11039,7 @@ class TooltipContainerComponent {
10991
11039
  this.tooltipPosition = computed(() => this.tooltipStateSignal().position, ...(ngDevMode ? [{ debugName: "tooltipPosition" }] : []));
10992
11040
  this.targetRect = computed(() => this.tooltipStateSignal().targetRect, ...(ngDevMode ? [{ debugName: "targetRect" }] : []));
10993
11041
  this.mousePosition = computed(() => this.tooltipStateSignal().mousePosition, ...(ngDevMode ? [{ debugName: "mousePosition" }] : []));
11042
+ this.scrollContainer = computed(() => this.tooltipStateSignal().scrollContainer, ...(ngDevMode ? [{ debugName: "scrollContainer" }] : []));
10994
11043
  // Type-safe content accessors for each tooltip type
10995
11044
  this.metricContent = computed(() => {
10996
11045
  const content = this.tooltipContent();
@@ -11036,6 +11085,8 @@ class TooltipContainerComponent {
11036
11085
  return true;
11037
11086
  const position = this.tooltipPosition();
11038
11087
  const tooltipWidth = 384;
11088
+ const container = this.scrollContainer();
11089
+ const viewportWidth = container?.clientWidth || window.innerWidth;
11039
11090
  // For 'auto' positioning, always center horizontally
11040
11091
  if (position === 'auto') {
11041
11092
  return true;
@@ -11044,7 +11095,7 @@ class TooltipContainerComponent {
11044
11095
  const centerPosition = rect.left + rect.width / 2;
11045
11096
  const halfWidth = tooltipWidth / 2;
11046
11097
  const wouldGoOffLeft = centerPosition - halfWidth < 10;
11047
- const wouldGoOffRight = centerPosition + halfWidth > window.innerWidth - 10;
11098
+ const wouldGoOffRight = centerPosition + halfWidth > viewportWidth - 10;
11048
11099
  return !wouldGoOffLeft && !wouldGoOffRight;
11049
11100
  }
11050
11101
  return false;
@@ -11066,7 +11117,8 @@ class TooltipContainerComponent {
11066
11117
  this.tooltipStateSignal.set({
11067
11118
  ...state,
11068
11119
  mousePosition: state.mousePosition || undefined,
11069
- content: state.content ?? undefined
11120
+ content: state.content ?? undefined,
11121
+ scrollContainer: state.scrollContainer || null
11070
11122
  });
11071
11123
  });
11072
11124
  effect(() => {
@@ -11078,6 +11130,14 @@ class TooltipContainerComponent {
11078
11130
  }
11079
11131
  });
11080
11132
  }
11133
+ onTooltipMouseEnter() {
11134
+ this.tooltipService.setTooltipHovered(true);
11135
+ }
11136
+ onTooltipMouseLeave() {
11137
+ this.tooltipService.setTooltipHovered(false);
11138
+ // Trigger the hide check callback from the directive
11139
+ this.tooltipService.triggerHideCheck();
11140
+ }
11081
11141
  calculateLeft() {
11082
11142
  const rect = this.targetRect();
11083
11143
  if (!rect)
@@ -11085,6 +11145,8 @@ class TooltipContainerComponent {
11085
11145
  const position = this.tooltipPosition();
11086
11146
  const mousePos = this.mousePosition();
11087
11147
  const tooltipWidth = 384;
11148
+ const container = this.scrollContainer();
11149
+ const viewportWidth = container?.clientWidth || window.innerWidth;
11088
11150
  // Handle 'auto' positioning with mouse coordinates
11089
11151
  if (position === 'auto' && mousePos) {
11090
11152
  const halfWidth = tooltipWidth / 2;
@@ -11093,8 +11155,8 @@ class TooltipContainerComponent {
11093
11155
  if (leftPos - halfWidth < 10) {
11094
11156
  leftPos = halfWidth + 10;
11095
11157
  }
11096
- else if (leftPos + halfWidth > window.innerWidth - 10) {
11097
- leftPos = window.innerWidth - halfWidth - 10;
11158
+ else if (leftPos + halfWidth > viewportWidth - 10) {
11159
+ leftPos = viewportWidth - halfWidth - 10;
11098
11160
  }
11099
11161
  return leftPos;
11100
11162
  }
@@ -11105,14 +11167,14 @@ class TooltipContainerComponent {
11105
11167
  const halfWidth = tooltipWidth / 2;
11106
11168
  // Check if centered tooltip would go off screen
11107
11169
  const wouldGoOffLeft = centerPosition - halfWidth < 10;
11108
- const wouldGoOffRight = centerPosition + halfWidth > window.innerWidth - 10;
11170
+ const wouldGoOffRight = centerPosition + halfWidth > viewportWidth - 10;
11109
11171
  if (wouldGoOffLeft) {
11110
11172
  // Align to left edge with padding
11111
11173
  return 10;
11112
11174
  }
11113
11175
  else if (wouldGoOffRight) {
11114
11176
  // Align to right edge with padding
11115
- return window.innerWidth - tooltipWidth - 10;
11177
+ return viewportWidth - tooltipWidth - 10;
11116
11178
  }
11117
11179
  else {
11118
11180
  // Center normally (transform will be applied)
@@ -11130,7 +11192,7 @@ class TooltipContainerComponent {
11130
11192
  case 'right': {
11131
11193
  const rightPosition = rect.right + 8;
11132
11194
  // If tooltip would go off right edge, position it to the left instead
11133
- if (rightPosition + tooltipWidth > window.innerWidth - 10) {
11195
+ if (rightPosition + tooltipWidth > viewportWidth - 10) {
11134
11196
  return rect.left - tooltipWidth - 8;
11135
11197
  }
11136
11198
  return rightPosition;
@@ -11146,6 +11208,8 @@ class TooltipContainerComponent {
11146
11208
  const position = this.tooltipPosition();
11147
11209
  const mousePos = this.mousePosition();
11148
11210
  const type = this.tooltipType();
11211
+ const container = this.scrollContainer();
11212
+ const viewportHeight = container?.clientHeight || window.innerHeight;
11149
11213
  // Estimate tooltip height based on type
11150
11214
  let estimatedHeight = 100;
11151
11215
  if (type === 'insight') {
@@ -11159,7 +11223,7 @@ class TooltipContainerComponent {
11159
11223
  const offset = 20; // Offset from mouse cursor
11160
11224
  let topPos = mousePos.y + offset;
11161
11225
  // If tooltip would go off bottom of screen, position above cursor
11162
- if (topPos + estimatedHeight > window.innerHeight - 10) {
11226
+ if (topPos + estimatedHeight > viewportHeight - 10) {
11163
11227
  topPos = mousePos.y - estimatedHeight - offset;
11164
11228
  }
11165
11229
  // Ensure it doesn't go off top
@@ -11180,7 +11244,7 @@ class TooltipContainerComponent {
11180
11244
  case 'bottom': {
11181
11245
  const bottomPosition = rect.bottom + 8;
11182
11246
  // If tooltip would go off bottom of screen, position it above instead
11183
- if (bottomPosition + estimatedHeight > window.innerHeight - 10) {
11247
+ if (bottomPosition + estimatedHeight > viewportHeight - 10) {
11184
11248
  return rect.top - estimatedHeight - 8;
11185
11249
  }
11186
11250
  return bottomPosition;
@@ -11189,7 +11253,7 @@ class TooltipContainerComponent {
11189
11253
  case 'right': {
11190
11254
  const centerPosition = rect.top + rect.height / 2 - estimatedHeight / 2;
11191
11255
  // Keep within viewport
11192
- const maxTop = window.innerHeight - estimatedHeight - 10;
11256
+ const maxTop = viewportHeight - estimatedHeight - 10;
11193
11257
  return Math.max(10, Math.min(centerPosition, maxTop));
11194
11258
  }
11195
11259
  default:
@@ -11197,7 +11261,7 @@ class TooltipContainerComponent {
11197
11261
  }
11198
11262
  }
11199
11263
  static { this.ɵfac = function TooltipContainerComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || TooltipContainerComponent)(); }; }
11200
- static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TooltipContainerComponent, selectors: [["symphiq-tooltip-container"]], decls: 1, vars: 1, consts: [[1, "fixed", "z-[100]", "pointer-events-none", 3, "left", "top", "ngClass"], [1, "fixed", "z-[100]", "pointer-events-none", 3, "ngClass"], [1, "rounded-lg", "shadow-2xl", "border", "backdrop-blur-xl", "px-4", "py-3", "max-w-sm", 3, "ngClass"], [1, "text-sm", "space-y-3", 3, "ngClass"], [1, "text-sm", "whitespace-pre-line", "leading-relaxed", 3, "ngClass"], [1, "text-sm", "whitespace-pre-line", 3, "ngClass"], [3, "content", "isLightMode"], [1, "space-y-2"], [1, "font-semibold"], [1, "whitespace-pre-line", "leading-relaxed"]], template: function TooltipContainerComponent_Template(rf, ctx) { if (rf & 1) {
11264
+ static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TooltipContainerComponent, selectors: [["symphiq-tooltip-container"]], decls: 1, vars: 1, consts: [[1, "fixed", "z-[100]", "pointer-events-none", 3, "left", "top", "ngClass"], [1, "fixed", "z-[100]", "pointer-events-none", 3, "ngClass"], [1, "rounded-lg", "shadow-2xl", "border", "backdrop-blur-xl", "px-4", "py-3", "max-w-sm", "pointer-events-auto", 3, "mouseenter", "mouseleave", "ngClass"], [1, "text-sm", "space-y-3", 3, "ngClass"], [1, "text-sm", "whitespace-pre-line", "leading-relaxed", 3, "ngClass"], [1, "text-sm", "whitespace-pre-line", 3, "ngClass"], [3, "content", "isLightMode"], [1, "space-y-2"], [1, "font-semibold"], [1, "whitespace-pre-line", "leading-relaxed"]], template: function TooltipContainerComponent_Template(rf, ctx) { if (rf & 1) {
11201
11265
  i0.ɵɵconditionalCreate(0, TooltipContainerComponent_Conditional_0_Template, 15, 9, "div", 0);
11202
11266
  } if (rf & 2) {
11203
11267
  i0.ɵɵconditional(ctx.isVisible() ? 0 : -1);
@@ -11258,7 +11322,11 @@ class TooltipContainerComponent {
11258
11322
  [ngClass]="containerClass()"
11259
11323
  class="fixed z-[100] pointer-events-none"
11260
11324
  @fadeInScale>
11261
- <div [ngClass]="contentClass()" class="rounded-lg shadow-2xl border backdrop-blur-xl px-4 py-3 max-w-sm">
11325
+ <div
11326
+ [ngClass]="contentClass()"
11327
+ class="rounded-lg shadow-2xl border backdrop-blur-xl px-4 py-3 max-w-sm pointer-events-auto"
11328
+ (mouseenter)="onTooltipMouseEnter()"
11329
+ (mouseleave)="onTooltipMouseLeave()">
11262
11330
  @let content = tooltipContent();
11263
11331
  @switch (tooltipType()) {
11264
11332
  @case ('metric') {
@@ -11357,7 +11425,7 @@ class TooltipContainerComponent {
11357
11425
  `
11358
11426
  }]
11359
11427
  }], () => [], null); })();
11360
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TooltipContainerComponent, { className: "TooltipContainerComponent", filePath: "lib/components/funnel-analysis-dashboard/tooltip/tooltip-container.component.ts", lineNumber: 169 }); })();
11428
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TooltipContainerComponent, { className: "TooltipContainerComponent", filePath: "lib/components/funnel-analysis-dashboard/tooltip/tooltip-container.component.ts", lineNumber: 173 }); })();
11361
11429
 
11362
11430
  function MobileFABComponent_Conditional_1_Conditional_5_Template(rf, ctx) { if (rf & 1) {
11363
11431
  i0.ɵɵnamespaceSVG();
@@ -19172,7 +19240,7 @@ class SearchBarComponent {
19172
19240
  i0.ɵɵconditionalCreate(0, SearchBarComponent_Conditional_0_Template, 17, 10, "div", 1);
19173
19241
  } if (rf & 2) {
19174
19242
  i0.ɵɵconditional(ctx.searchService.isSearchOpen() ? 0 : -1);
19175
- } }, dependencies: [CommonModule, i1.NgClass, FormsModule, i5.DefaultValueAccessor, i5.NgControlStatus, i5.NgModel], styles: ["[_nghost-%COMP%]{display:contents}"], changeDetection: 0 }); }
19243
+ } }, dependencies: [CommonModule, i1.NgClass, FormsModule, i6.DefaultValueAccessor, i6.NgControlStatus, i6.NgModel], styles: ["[_nghost-%COMP%]{display:contents}"], changeDetection: 0 }); }
19176
19244
  }
19177
19245
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SearchBarComponent, [{
19178
19246
  type: Component,
@@ -21007,10 +21075,11 @@ function SymphiqFunnelAnalysisDashboardComponent_Conditional_80_Template(rf, ctx
21007
21075
  i0.ɵɵproperty("metrics", ctx_r2.competitiveMetrics())("allCharts", ctx_r2.allCharts())("isLightMode", ctx_r2.isLightMode())("isCompactMode", ctx_r2.viewModeService.isCompact());
21008
21076
  } }
21009
21077
  class SymphiqFunnelAnalysisDashboardComponent {
21010
- constructor(funnelOrderService, viewModeService, searchService) {
21078
+ constructor(funnelOrderService, viewModeService, searchService, tooltipService) {
21011
21079
  this.funnelOrderService = funnelOrderService;
21012
21080
  this.viewModeService = viewModeService;
21013
21081
  this.searchService = searchService;
21082
+ this.tooltipService = tooltipService;
21014
21083
  // Input signals (Angular 20 signal-based inputs)
21015
21084
  this.requestedByUser = input(...(ngDevMode ? [undefined, { debugName: "requestedByUser" }] : []));
21016
21085
  this.viewMode = input(ViewModeEnum.LIGHT, ...(ngDevMode ? [{ debugName: "viewMode" }] : []));
@@ -21040,6 +21109,7 @@ class SymphiqFunnelAnalysisDashboardComponent {
21040
21109
  this.isProgrammaticScroll = false;
21041
21110
  this.lastStateChangeTime = 0;
21042
21111
  this.STATE_CHANGE_COOLDOWN = 500; // milliseconds
21112
+ this.embeddedScrollContainer = null;
21043
21113
  // Mobile navigation sections
21044
21114
  this.navSections = [
21045
21115
  { id: 'overview', label: 'Overview', icon: 'overview' },
@@ -21360,169 +21430,80 @@ class SymphiqFunnelAnalysisDashboardComponent {
21360
21430
  // Set up container scroll listener when in embedded mode
21361
21431
  if (this.embedded()) {
21362
21432
  let container = null;
21363
- console.log('[Scroll Debug v6] Embedded mode detected, setting up scroll listener');
21364
- console.log('[Scroll Debug v6] scrollContainerId:', this.scrollContainerId());
21365
21433
  // If a scroll container ID is provided, use that element
21366
21434
  if (this.scrollContainerId()) {
21367
- const element = document.getElementById(this.scrollContainerId());
21368
- if (!element) {
21369
- console.warn(`[Scroll Debug] Scroll container with id "${this.scrollContainerId()}" not found. Scroll tracking will not work.`);
21370
- }
21371
- else {
21372
- console.log('[Scroll Debug] Found external scroll container:', element);
21373
- console.log('[Scroll Debug] Container tag name:', element.tagName);
21435
+ const setupScrollContainer = () => {
21436
+ const element = document.getElementById(this.scrollContainerId());
21437
+ if (!element) {
21438
+ return false;
21439
+ }
21374
21440
  // Check if this is an Ionic ion-content element
21375
21441
  if (element.tagName.toLowerCase() === 'ion-content') {
21376
- console.log('[Scroll Debug] Detected Ionic ion-content, attempting to get scrollable element');
21377
- // Try to get the scroll element from Ionic's ion-content
21378
- // Ionic stores the scrollable element in the component itself
21379
21442
  const ionContent = element;
21380
- // Method 1: Try getScrollElement() method (Ionic 4+)
21443
+ // Get the scrollable element from Ionic
21381
21444
  if (typeof ionContent.getScrollElement === 'function') {
21382
- console.log('[Scroll Debug] Using getScrollElement() method');
21383
21445
  ionContent.getScrollElement().then((scrollElement) => {
21384
- console.log('[Scroll Debug] Got Ionic scroll element:', scrollElement);
21385
- const dimensions = {
21386
- clientHeight: scrollElement.clientHeight,
21387
- scrollHeight: scrollElement.scrollHeight,
21388
- scrollTop: scrollElement.scrollTop,
21389
- hasOverflow: scrollElement.scrollHeight > scrollElement.clientHeight
21390
- };
21391
- console.log('[Scroll Debug] Ionic scroll element dimensions:', dimensions);
21392
- if (!dimensions.hasOverflow) {
21393
- console.warn('[Scroll Debug] ⚠️ WARNING: Scroll element has no overflow! Content may still be loading.');
21394
- console.warn('[Scroll Debug] The dashboard content needs to be taller than', dimensions.clientHeight, 'px to enable scrolling');
21395
- // Diagnostic: Check the dashboard component's actual rendered height
21396
- setTimeout(() => {
21397
- const dashboardEl = this.dashboardContainer?.nativeElement;
21398
- if (dashboardEl) {
21399
- const styles = window.getComputedStyle(dashboardEl);
21400
- console.log('[Scroll Debug] Dashboard container computed styles:', {
21401
- height: styles.height,
21402
- minHeight: styles.minHeight,
21403
- maxHeight: styles.maxHeight,
21404
- display: styles.display,
21405
- position: styles.position,
21406
- overflow: styles.overflow,
21407
- scrollHeight: dashboardEl.scrollHeight,
21408
- clientHeight: dashboardEl.clientHeight,
21409
- offsetHeight: dashboardEl.offsetHeight
21410
- });
21411
- // Check children
21412
- const children = Array.from(dashboardEl.children);
21413
- console.log('[Scroll Debug] Dashboard has', children.length, 'direct children');
21414
- children.forEach((child, idx) => {
21415
- const childStyles = window.getComputedStyle(child);
21416
- console.log(`[Scroll Debug] Child ${idx}:`, {
21417
- tagName: child.tagName,
21418
- height: childStyles.height,
21419
- display: childStyles.display,
21420
- clientHeight: child.clientHeight,
21421
- scrollHeight: child.scrollHeight
21422
- });
21423
- });
21424
- // Check if content is actually rendering
21425
- const hasVisibleContent = dashboardEl.scrollHeight > 100;
21426
- if (!hasVisibleContent) {
21427
- console.error('[Scroll Debug] ⚠️ CRITICAL: Dashboard has minimal content! Check if funnelAnalysis data is loaded.');
21428
- }
21429
- }
21430
- }, 100);
21431
- // Set up a MutationObserver to detect when content becomes scrollable
21432
- const observer = new MutationObserver(() => {
21433
- const newScrollHeight = scrollElement.scrollHeight;
21434
- const newClientHeight = scrollElement.clientHeight;
21435
- if (newScrollHeight > newClientHeight) {
21436
- console.log('[Scroll Debug] ✓ Content is now scrollable!', {
21437
- clientHeight: newClientHeight,
21438
- scrollHeight: newScrollHeight,
21439
- hasOverflow: true
21440
- });
21441
- observer.disconnect();
21442
- }
21443
- });
21444
- observer.observe(scrollElement, {
21445
- childList: true,
21446
- subtree: true,
21447
- attributes: true
21448
- });
21449
- console.log('[Scroll Debug] Monitoring for content changes...');
21450
- }
21451
- // Attach to BOTH the inner scroll element AND the ion-content itself
21446
+ // Store reference for use in scrollToSection and tooltips
21447
+ this.embeddedScrollContainer = scrollElement;
21448
+ this.tooltipService.setScrollContainer(scrollElement);
21449
+ // Attach scroll listeners
21452
21450
  scrollElement.addEventListener('scroll', () => this.onContainerScroll(scrollElement), { passive: true });
21453
- console.log('[Scroll Debug] Attached scroll listener to Ionic inner scroll element');
21454
- // ALSO listen to ion-content's ionScroll event (Ionic's custom scroll event)
21455
- ionContent.addEventListener('ionScroll', (event) => {
21456
- console.log('[Scroll Debug] ionScroll event fired:', event.detail?.scrollTop);
21457
- this.onContainerScroll(scrollElement);
21458
- }, { passive: true });
21459
- console.log('[Scroll Debug] Attached ionScroll listener to ion-content');
21460
- // FALLBACK: Poll scroll position directly from scrollElement
21461
- // This ensures we catch scroll events even if neither 'scroll' nor 'ionScroll' fire
21451
+ ionContent.addEventListener('ionScroll', () => this.onContainerScroll(scrollElement), { passive: true });
21452
+ // Polling fallback for reliable scroll detection
21462
21453
  let lastScrollTop = 0;
21463
- let pollCount = 0;
21464
21454
  const pollScroll = () => {
21465
- pollCount++;
21466
21455
  const currentScrollTop = scrollElement.scrollTop;
21467
21456
  if (currentScrollTop !== lastScrollTop) {
21468
- console.log('[Scroll Debug v6] Poll #', pollCount, '- Scroll detected:', lastScrollTop, '->', currentScrollTop);
21469
21457
  lastScrollTop = currentScrollTop;
21470
21458
  this.onContainerScroll(scrollElement);
21471
21459
  }
21472
21460
  requestAnimationFrame(pollScroll);
21473
21461
  };
21474
21462
  requestAnimationFrame(pollScroll);
21475
- console.log('[Scroll Debug v6] Started polling scrollElement.scrollTop directly');
21476
- // Test if scroll event fires by manually triggering a test
21477
- console.log('[Scroll Debug] Testing scroll event by programmatically scrolling to 1px...');
21478
- scrollElement.scrollTop = 1;
21479
- setTimeout(() => {
21480
- if (scrollElement.scrollTop === 1) {
21481
- console.log('[Scroll Debug] ✓ Scroll position changed successfully');
21482
- scrollElement.scrollTop = 0; // Reset
21483
- }
21484
- else {
21485
- console.warn('[Scroll Debug] ⚠️ Could not change scroll position - element may not be scrollable');
21486
- }
21487
- }, 50);
21488
21463
  }).catch((error) => {
21489
- console.error('[Scroll Debug] Error getting Ionic scroll element:', error);
21464
+ console.error('Error getting Ionic scroll element:', error);
21490
21465
  });
21466
+ return true;
21491
21467
  }
21492
21468
  else {
21493
- console.warn('[Scroll Debug] ion-content does not have getScrollElement() method');
21494
- // Fallback: attach to ion-content itself (may not work)
21495
- container = element;
21469
+ // Fallback for older Ionic versions
21470
+ this.embeddedScrollContainer = element;
21471
+ this.tooltipService.setScrollContainer(element);
21472
+ element.addEventListener('scroll', () => this.onContainerScroll(element), { passive: true });
21473
+ return true;
21496
21474
  }
21497
21475
  }
21498
21476
  else {
21499
21477
  // Regular HTML element
21500
- container = element;
21501
- console.log('[Scroll Debug] Container dimensions:', {
21502
- clientHeight: container.clientHeight,
21503
- scrollHeight: container.scrollHeight,
21504
- scrollTop: container.scrollTop,
21505
- hasOverflow: container.scrollHeight > container.clientHeight
21506
- });
21478
+ this.embeddedScrollContainer = element;
21479
+ this.tooltipService.setScrollContainer(element);
21480
+ element.addEventListener('scroll', () => this.onContainerScroll(element), { passive: true });
21481
+ return true;
21507
21482
  }
21483
+ };
21484
+ // Try to set up immediately
21485
+ if (!setupScrollContainer()) {
21486
+ // If not found, retry with delays (element might not be in DOM yet)
21487
+ let retries = 0;
21488
+ const maxRetries = 10;
21489
+ const retryInterval = setInterval(() => {
21490
+ retries++;
21491
+ if (setupScrollContainer() || retries >= maxRetries) {
21492
+ clearInterval(retryInterval);
21493
+ if (retries >= maxRetries) {
21494
+ console.warn(`Scroll container with id "${this.scrollContainerId()}" not found after ${maxRetries} retries.`);
21495
+ }
21496
+ }
21497
+ }, 100);
21508
21498
  }
21509
21499
  }
21510
21500
  else if (this.dashboardContainer) {
21511
21501
  // Fall back to internal dashboard container
21512
21502
  container = this.dashboardContainer.nativeElement;
21513
- console.log('[Scroll Debug] Using internal dashboard container:', container);
21514
- }
21515
- // Only attach listener for non-Ionic elements (Ionic handled above)
21516
- if (container) {
21517
- console.log('[Scroll Debug] Attaching scroll listener to container');
21503
+ this.embeddedScrollContainer = container;
21504
+ this.tooltipService.setScrollContainer(container);
21518
21505
  container.addEventListener('scroll', () => this.onContainerScroll(container), { passive: true });
21519
21506
  }
21520
- else if (!this.scrollContainerId() || document.getElementById(this.scrollContainerId())?.tagName.toLowerCase() !== 'ion-content') {
21521
- console.warn('[Scroll Debug] No scroll container found!');
21522
- }
21523
- }
21524
- else {
21525
- console.log('[Scroll Debug] Not in embedded mode, using window scroll');
21526
21507
  }
21527
21508
  }
21528
21509
  handleSearchResult(result) {
@@ -21732,66 +21713,42 @@ class SymphiqFunnelAnalysisDashboardComponent {
21732
21713
  }
21733
21714
  onContainerScroll(container) {
21734
21715
  if (!this.embedded()) {
21735
- console.log('[Scroll Debug] onContainerScroll called but not in embedded mode, returning');
21736
21716
  return;
21737
21717
  }
21738
21718
  const scrollPosition = container.scrollTop;
21739
- // Update scroll progress immediately for smooth bar animation
21719
+ // Update scroll progress for smooth bar animation
21740
21720
  const containerHeight = container.clientHeight;
21741
21721
  const scrollHeight = container.scrollHeight;
21742
21722
  const maxScroll = scrollHeight - containerHeight;
21743
21723
  const progress = maxScroll > 0 ? (scrollPosition / maxScroll) * 100 : 0;
21744
- console.log('[Scroll Debug] Container scroll event:', {
21745
- scrollPosition,
21746
- containerHeight,
21747
- scrollHeight,
21748
- maxScroll,
21749
- progress: progress.toFixed(2) + '%',
21750
- currentScrollProgress: this.scrollProgress(),
21751
- isScrolled: this.isScrolled()
21752
- });
21753
21724
  this.scrollProgress.set(Math.min(100, Math.max(0, progress)));
21754
- // Skip header state updates during programmatic scrolling
21725
+ // Skip header state updates during programmatic scrolling or cooldown
21755
21726
  if (this.isProgrammaticScroll) {
21756
- console.log('[Scroll Debug] Skipping header update - programmatic scroll');
21757
21727
  return;
21758
21728
  }
21759
- // Check if we're in cooldown period after a recent state change
21760
21729
  const now = Date.now();
21761
21730
  const timeSinceLastChange = now - this.lastStateChangeTime;
21762
21731
  if (timeSinceLastChange < this.STATE_CHANGE_COOLDOWN) {
21763
- console.log('[Scroll Debug] Skipping header update - in cooldown period', timeSinceLastChange + 'ms');
21764
21732
  return;
21765
21733
  }
21766
- // Clear any pending timeout
21767
21734
  if (this.scrollTimeout) {
21768
21735
  clearTimeout(this.scrollTimeout);
21769
21736
  }
21770
- // Use hysteresis (two different thresholds) to prevent bounce loop
21737
+ // Use hysteresis to prevent bounce
21771
21738
  const COLLAPSE_THRESHOLD = 50;
21772
21739
  const EXPAND_THRESHOLD = 30;
21773
21740
  const currentState = this.isScrolled();
21774
- // Determine new state based on current position and hysteresis logic
21775
21741
  let newState = currentState;
21776
- // If currently expanded and we scroll past collapse threshold, collapse
21777
21742
  if (!currentState && scrollPosition > COLLAPSE_THRESHOLD) {
21778
21743
  newState = true;
21779
- console.log('[Scroll Debug] Triggering COLLAPSE - scrollPosition:', scrollPosition, '> threshold:', COLLAPSE_THRESHOLD);
21780
21744
  }
21781
- // If currently collapsed and we scroll back above expand threshold, expand
21782
21745
  else if (currentState && scrollPosition < EXPAND_THRESHOLD) {
21783
21746
  newState = false;
21784
- console.log('[Scroll Debug] Triggering EXPAND - scrollPosition:', scrollPosition, '< threshold:', EXPAND_THRESHOLD);
21785
21747
  }
21786
- // Only update state if it actually changed
21787
21748
  if (newState !== currentState) {
21788
- console.log('[Scroll Debug] State change detected:', currentState, '->', newState);
21789
21749
  this.isScrolled.set(newState);
21790
21750
  this.lastStateChangeTime = Date.now();
21791
21751
  }
21792
- else {
21793
- console.log('[Scroll Debug] No state change needed. Current state:', currentState, 'Position:', scrollPosition);
21794
- }
21795
21752
  this.lastScrollPosition = scrollPosition;
21796
21753
  }
21797
21754
  onScroll() {
@@ -21859,48 +21816,60 @@ class SymphiqFunnelAnalysisDashboardComponent {
21859
21816
  this.lastScrollPosition = scrollPosition;
21860
21817
  }
21861
21818
  scrollToTop() {
21862
- // Set flag to prevent header state changes during smooth scroll
21863
21819
  this.isProgrammaticScroll = true;
21864
- window.scrollTo({
21865
- top: 0,
21866
- behavior: 'smooth'
21867
- });
21868
- // Clear flag after smooth scroll completes
21820
+ if (this.embedded() && this.embeddedScrollContainer) {
21821
+ this.embeddedScrollContainer.scrollTo({
21822
+ top: 0,
21823
+ behavior: 'smooth'
21824
+ });
21825
+ }
21826
+ else {
21827
+ window.scrollTo({
21828
+ top: 0,
21829
+ behavior: 'smooth'
21830
+ });
21831
+ }
21869
21832
  setTimeout(() => {
21870
21833
  this.isProgrammaticScroll = false;
21871
- // At top of page, header should be expanded
21872
21834
  this.isScrolled.set(false);
21873
21835
  }, 800);
21874
21836
  }
21875
21837
  scrollToSection(sectionId) {
21876
21838
  const element = document.getElementById(sectionId);
21877
- if (element) {
21878
- // Use smaller offset for overall-section to stay below expand threshold
21879
- // Other sections use larger offset to stay above collapse threshold
21880
- const offset = sectionId === 'overall-section' ? 50 : 100;
21839
+ if (!element)
21840
+ return;
21841
+ const offset = sectionId === 'overall-section' ? 50 : 100;
21842
+ this.isProgrammaticScroll = true;
21843
+ if (this.embedded() && this.embeddedScrollContainer) {
21844
+ // For embedded mode: calculate relative scroll position
21845
+ const elementTop = element.offsetTop;
21846
+ const scrollTop = elementTop - offset;
21847
+ this.embeddedScrollContainer.scrollTo({
21848
+ top: scrollTop,
21849
+ behavior: 'smooth'
21850
+ });
21851
+ }
21852
+ else {
21853
+ // For window mode: use getBoundingClientRect
21881
21854
  const elementPosition = element.getBoundingClientRect().top + window.pageYOffset;
21882
21855
  const offsetPosition = elementPosition - offset;
21883
- // Set flag to prevent header state changes during smooth scroll
21884
- this.isProgrammaticScroll = true;
21885
21856
  window.scrollTo({
21886
21857
  top: offsetPosition,
21887
21858
  behavior: 'smooth'
21888
21859
  });
21889
- // Clear flag after smooth scroll completes (approximate duration)
21890
- setTimeout(() => {
21891
- this.isProgrammaticScroll = false;
21892
- // Manually set the correct header state after scroll completes
21893
- // Use hysteresis thresholds: collapse at 150px, expand below 100px
21894
- const finalPosition = window.scrollY;
21895
- if (finalPosition > 150) {
21896
- this.isScrolled.set(true);
21897
- }
21898
- else if (finalPosition < 100) {
21899
- this.isScrolled.set(false);
21900
- }
21901
- // If between 100-150, leave current state unchanged
21902
- }, 800);
21903
21860
  }
21861
+ setTimeout(() => {
21862
+ this.isProgrammaticScroll = false;
21863
+ const finalPosition = this.embedded() && this.embeddedScrollContainer
21864
+ ? this.embeddedScrollContainer.scrollTop
21865
+ : window.scrollY;
21866
+ if (finalPosition > 50) {
21867
+ this.isScrolled.set(true);
21868
+ }
21869
+ else if (finalPosition < 30) {
21870
+ this.isScrolled.set(false);
21871
+ }
21872
+ }, 800);
21904
21873
  }
21905
21874
  handleMobileNavigation(sectionId) {
21906
21875
  this.activeNavSection.set(sectionId);
@@ -21943,7 +21912,7 @@ class SymphiqFunnelAnalysisDashboardComponent {
21943
21912
  }
21944
21913
  return value.toLocaleString('en-US', { maximumFractionDigits: 0 });
21945
21914
  }
21946
- static { this.ɵfac = function SymphiqFunnelAnalysisDashboardComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SymphiqFunnelAnalysisDashboardComponent)(i0.ɵɵdirectiveInject(FunnelOrderService), i0.ɵɵdirectiveInject(ViewModeService), i0.ɵɵdirectiveInject(SearchService)); }; }
21915
+ static { this.ɵfac = function SymphiqFunnelAnalysisDashboardComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SymphiqFunnelAnalysisDashboardComponent)(i0.ɵɵdirectiveInject(FunnelOrderService), i0.ɵɵdirectiveInject(ViewModeService), i0.ɵɵdirectiveInject(SearchService), i0.ɵɵdirectiveInject(TooltipService)); }; }
21947
21916
  static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SymphiqFunnelAnalysisDashboardComponent, selectors: [["symphiq-funnel-analysis-dashboard"]], viewQuery: function SymphiqFunnelAnalysisDashboardComponent_Query(rf, ctx) { if (rf & 1) {
21948
21917
  i0.ɵɵviewQuery(ModalComponent, 5);
21949
21918
  i0.ɵɵviewQuery(_c0$4, 5);
@@ -22157,7 +22126,7 @@ class SymphiqFunnelAnalysisDashboardComponent {
22157
22126
  i0.ɵɵproperty("isLightMode", ctx.isLightMode())("isCompactMode", ctx.viewModeService.isCompact())("isExpanded", ctx.fabExpanded());
22158
22127
  i0.ɵɵadvance();
22159
22128
  i0.ɵɵproperty("isLightMode", ctx.isLightMode())("sections", ctx.navSections)("activeSection", ctx.activeNavSection());
22160
- } }, dependencies: [CommonModule, i1.NgClass, FormsModule, i5.NgSelectOption, i5.ɵNgSelectMultipleOption, i5.SelectControlValueAccessor, i5.NgControlStatus, i5.NgModel, OverallAssessmentComponent,
22129
+ } }, dependencies: [CommonModule, i1.NgClass, FormsModule, i6.NgSelectOption, i6.ɵNgSelectMultipleOption, i6.SelectControlValueAccessor, i6.NgControlStatus, i6.NgModel, OverallAssessmentComponent,
22161
22130
  InsightCardComponent,
22162
22131
  MetricCardComponent,
22163
22132
  BreakdownSectionComponent,
@@ -22944,7 +22913,7 @@ class SymphiqFunnelAnalysisDashboardComponent {
22944
22913
 
22945
22914
  </div>
22946
22915
  `, styles: [":host{display:block;min-height:100%}.bg-gradient-radial{background:radial-gradient(circle,var(--tw-gradient-stops))}.bento-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.5rem;grid-auto-flow:dense}@media (min-width: 768px){.bento-grid{grid-template-columns:repeat(6,1fr)}}.bento-small{grid-column:span 2;grid-row:span 1}.bento-medium{grid-column:span 3;grid-row:span 1}.bento-large{grid-column:span 4;grid-row:span 1}.bento-featured{grid-column:span 6;grid-row:span 1}@media (max-width: 767px){.bento-small,.bento-medium,.bento-large,.bento-featured{grid-column:span 1}}.masonry-grid{column-count:1;column-gap:1.5rem}@media (min-width: 768px){.masonry-grid{column-count:2}}@media (min-width: 1280px){.masonry-grid{column-count:3}}.masonry-grid>div{break-inside:avoid;margin-bottom:1.5rem}.masonry-featured{column-span:all}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-hide::-webkit-scrollbar{display:none}@media (max-width: 640px){.animate-fade-in-up{animation-duration:.4s}}\n"] }]
22947
- }], () => [{ type: FunnelOrderService }, { type: ViewModeService }, { type: SearchService }], { modalComponent: [{
22916
+ }], () => [{ type: FunnelOrderService }, { type: ViewModeService }, { type: SearchService }, { type: TooltipService }], { modalComponent: [{
22948
22917
  type: ViewChild,
22949
22918
  args: [ModalComponent]
22950
22919
  }], dashboardContainer: [{
@@ -22954,7 +22923,7 @@ class SymphiqFunnelAnalysisDashboardComponent {
22954
22923
  type: HostListener,
22955
22924
  args: ['window:scroll', ['$event']]
22956
22925
  }] }); })();
22957
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SymphiqFunnelAnalysisDashboardComponent, { className: "SymphiqFunnelAnalysisDashboardComponent", filePath: "lib/components/funnel-analysis-dashboard/symphiq-funnel-analysis-dashboard.component.ts", lineNumber: 915 }); })();
22926
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SymphiqFunnelAnalysisDashboardComponent, { className: "SymphiqFunnelAnalysisDashboardComponent", filePath: "lib/components/funnel-analysis-dashboard/symphiq-funnel-analysis-dashboard.component.ts", lineNumber: 916 }); })();
22958
22927
 
22959
22928
  /**
22960
22929
  * Shared Theme Color Utilities