@eric-emg/symphiq-components 1.2.58 → 1.2.60

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.
@@ -1,4 +1,4 @@
1
- import { CompetitiveScoreEnum, ViewModeEnum, normalizeToV3, LineChartUseCaseEnum } from '@jebgem/model';
1
+ import { CompetitiveScoreEnum, ViewModeEnum, normalizeToV3, MetricEnumUtil, LineChartUseCaseEnum } from '@jebgem/model';
2
2
  export * from '@jebgem/model';
3
3
  import * as i0 from '@angular/core';
4
4
  import { Injectable, signal, input, ChangeDetectionStrategy, Component, output, computed, inject, ElementRef, Renderer2, effect, Directive, HostListener, untracked, ViewChild } from '@angular/core';
@@ -193,35 +193,48 @@ class TooltipService {
193
193
  // Insight tooltips can be quite tall, so need more space
194
194
  const HORIZONTAL_THRESHOLD = 200;
195
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;
196
+ // Calculate effective boundaries
197
+ let leftBound = 0;
198
+ let rightBound = window.innerWidth;
199
+ let topBound = 0;
200
+ let bottomBound = window.innerHeight;
201
+ // If there's a scroll container, adjust boundaries relative to it
202
+ if (this.scrollContainer) {
203
+ const containerRect = this.scrollContainer.getBoundingClientRect();
204
+ leftBound = containerRect.left;
205
+ rightBound = containerRect.right;
206
+ topBound = containerRect.top;
207
+ bottomBound = containerRect.bottom;
208
+ }
209
+ // Calculate space available around target element
210
+ const spaceLeft = targetRect.left - leftBound;
211
+ const spaceRight = rightBound - targetRect.right;
212
+ const spaceTop = targetRect.top - topBound;
213
+ const spaceBelow = bottomBound - targetRect.bottom;
199
214
  // Check left edge
200
- if (position === 'left' && targetRect.left < HORIZONTAL_THRESHOLD) {
215
+ if (position === 'left' && spaceLeft < HORIZONTAL_THRESHOLD) {
201
216
  adjustedPosition = 'right';
202
217
  }
203
218
  // Check right edge
204
- if (position === 'right' && (viewportWidth - targetRect.right) < HORIZONTAL_THRESHOLD) {
219
+ if (position === 'right' && spaceRight < HORIZONTAL_THRESHOLD) {
205
220
  adjustedPosition = 'left';
206
221
  }
207
222
  // Check top edge
208
- if (position === 'top' && targetRect.top < VERTICAL_THRESHOLD) {
223
+ if (position === 'top' && spaceTop < VERTICAL_THRESHOLD) {
209
224
  adjustedPosition = 'bottom';
210
225
  }
211
226
  // Check bottom edge - prioritize flipping to top if not enough space
212
- if (position === 'bottom' && (viewportHeight - targetRect.bottom) < VERTICAL_THRESHOLD) {
227
+ if (position === 'bottom' && spaceBelow < VERTICAL_THRESHOLD) {
213
228
  adjustedPosition = 'top';
214
229
  }
215
230
  // Smart auto-detection for 'top' position tooltips near bottom
216
231
  if (position === 'top') {
217
- const spaceBelow = viewportHeight - targetRect.bottom;
218
- const spaceAbove = targetRect.top;
219
232
  // If more space above and not enough space below, keep it on top
220
233
  // If more space below, switch to bottom
221
- if (spaceBelow > spaceAbove && spaceBelow > VERTICAL_THRESHOLD) {
234
+ if (spaceBelow > spaceTop && spaceBelow > VERTICAL_THRESHOLD) {
222
235
  adjustedPosition = 'bottom';
223
236
  }
224
- else if (spaceAbove < VERTICAL_THRESHOLD && spaceBelow > VERTICAL_THRESHOLD) {
237
+ else if (spaceTop < VERTICAL_THRESHOLD && spaceBelow > VERTICAL_THRESHOLD) {
225
238
  adjustedPosition = 'bottom';
226
239
  }
227
240
  }
@@ -11144,17 +11157,25 @@ class TooltipContainerComponent {
11144
11157
  const mousePos = this.mousePosition();
11145
11158
  const tooltipWidth = 384;
11146
11159
  const container = this.scrollContainer();
11147
- const viewportWidth = container?.clientWidth || window.innerWidth;
11160
+ // Calculate effective viewport boundaries
11161
+ let leftBound = 10;
11162
+ let rightBound = window.innerWidth - 10;
11163
+ // If there's a scroll container, constrain to its boundaries
11164
+ if (container) {
11165
+ const containerRect = container.getBoundingClientRect();
11166
+ leftBound = Math.max(10, containerRect.left + 10);
11167
+ rightBound = Math.min(window.innerWidth - 10, containerRect.right - 10);
11168
+ }
11148
11169
  // Handle 'auto' positioning with mouse coordinates
11149
11170
  if (position === 'auto' && mousePos) {
11150
11171
  const halfWidth = tooltipWidth / 2;
11151
11172
  let leftPos = mousePos.x;
11152
- // Keep tooltip in viewport
11153
- if (leftPos - halfWidth < 10) {
11154
- leftPos = halfWidth + 10;
11173
+ // Keep tooltip in bounds
11174
+ if (leftPos - halfWidth < leftBound) {
11175
+ leftPos = leftBound + halfWidth;
11155
11176
  }
11156
- else if (leftPos + halfWidth > viewportWidth - 10) {
11157
- leftPos = viewportWidth - halfWidth - 10;
11177
+ else if (leftPos + halfWidth > rightBound) {
11178
+ leftPos = rightBound - halfWidth;
11158
11179
  }
11159
11180
  return leftPos;
11160
11181
  }
@@ -11163,16 +11184,16 @@ class TooltipContainerComponent {
11163
11184
  case 'bottom': {
11164
11185
  const centerPosition = rect.left + rect.width / 2;
11165
11186
  const halfWidth = tooltipWidth / 2;
11166
- // Check if centered tooltip would go off screen
11167
- const wouldGoOffLeft = centerPosition - halfWidth < 10;
11168
- const wouldGoOffRight = centerPosition + halfWidth > viewportWidth - 10;
11187
+ // Check if centered tooltip would go off bounds
11188
+ const wouldGoOffLeft = centerPosition - halfWidth < leftBound;
11189
+ const wouldGoOffRight = centerPosition + halfWidth > rightBound;
11169
11190
  if (wouldGoOffLeft) {
11170
- // Align to left edge with padding
11171
- return 10;
11191
+ // Align to left bound
11192
+ return leftBound;
11172
11193
  }
11173
11194
  else if (wouldGoOffRight) {
11174
- // Align to right edge with padding
11175
- return viewportWidth - tooltipWidth - 10;
11195
+ // Align to right bound
11196
+ return rightBound - tooltipWidth;
11176
11197
  }
11177
11198
  else {
11178
11199
  // Center normally (transform will be applied)
@@ -11181,17 +11202,25 @@ class TooltipContainerComponent {
11181
11202
  }
11182
11203
  case 'left': {
11183
11204
  const leftPosition = rect.left - tooltipWidth - 8;
11184
- // If tooltip would go off left edge, position it to the right instead
11185
- if (leftPosition < 10) {
11186
- return rect.right + 8;
11205
+ // If tooltip would go off left bound, position it to the right instead
11206
+ if (leftPosition < leftBound) {
11207
+ const rightPosition = rect.right + 8;
11208
+ if (rightPosition + tooltipWidth > rightBound) {
11209
+ return leftBound;
11210
+ }
11211
+ return rightPosition;
11187
11212
  }
11188
11213
  return leftPosition;
11189
11214
  }
11190
11215
  case 'right': {
11191
11216
  const rightPosition = rect.right + 8;
11192
- // If tooltip would go off right edge, position it to the left instead
11193
- if (rightPosition + tooltipWidth > viewportWidth - 10) {
11194
- return rect.left - tooltipWidth - 8;
11217
+ // If tooltip would go off right bound, position it to the left instead
11218
+ if (rightPosition + tooltipWidth > rightBound) {
11219
+ const leftPosition = rect.left - tooltipWidth - 8;
11220
+ if (leftPosition < leftBound) {
11221
+ return leftBound;
11222
+ }
11223
+ return leftPosition;
11195
11224
  }
11196
11225
  return rightPosition;
11197
11226
  }
@@ -11207,7 +11236,15 @@ class TooltipContainerComponent {
11207
11236
  const mousePos = this.mousePosition();
11208
11237
  const type = this.tooltipType();
11209
11238
  const container = this.scrollContainer();
11210
- const viewportHeight = container?.clientHeight || window.innerHeight;
11239
+ // Calculate effective viewport boundaries
11240
+ let topBound = 10;
11241
+ let bottomBound = window.innerHeight - 10;
11242
+ // If there's a scroll container, constrain to its boundaries
11243
+ if (container) {
11244
+ const containerRect = container.getBoundingClientRect();
11245
+ topBound = Math.max(10, containerRect.top + 10);
11246
+ bottomBound = Math.min(window.innerHeight - 10, containerRect.bottom - 10);
11247
+ }
11211
11248
  // Estimate tooltip height based on type
11212
11249
  let estimatedHeight = 100;
11213
11250
  if (type === 'insight') {
@@ -11220,29 +11257,29 @@ class TooltipContainerComponent {
11220
11257
  if (position === 'auto' && mousePos) {
11221
11258
  const offset = 20; // Offset from mouse cursor
11222
11259
  let topPos = mousePos.y + offset;
11223
- // If tooltip would go off bottom of screen, position above cursor
11224
- if (topPos + estimatedHeight > viewportHeight - 10) {
11260
+ // If tooltip would go off bottom, position above cursor
11261
+ if (topPos + estimatedHeight > bottomBound) {
11225
11262
  topPos = mousePos.y - estimatedHeight - offset;
11226
11263
  }
11227
11264
  // Ensure it doesn't go off top
11228
- if (topPos < 10) {
11229
- topPos = 10;
11265
+ if (topPos < topBound) {
11266
+ topPos = topBound;
11230
11267
  }
11231
11268
  return topPos;
11232
11269
  }
11233
11270
  switch (position) {
11234
11271
  case 'top': {
11235
11272
  const topPosition = rect.top - estimatedHeight - 8;
11236
- // If tooltip would go off top of screen, position it below instead
11237
- if (topPosition < 10) {
11273
+ // If tooltip would go off top bound, position it below instead
11274
+ if (topPosition < topBound) {
11238
11275
  return rect.bottom + 8;
11239
11276
  }
11240
11277
  return topPosition;
11241
11278
  }
11242
11279
  case 'bottom': {
11243
11280
  const bottomPosition = rect.bottom + 8;
11244
- // If tooltip would go off bottom of screen, position it above instead
11245
- if (bottomPosition + estimatedHeight > viewportHeight - 10) {
11281
+ // If tooltip would go off bottom bound, position it above instead
11282
+ if (bottomPosition + estimatedHeight > bottomBound) {
11246
11283
  return rect.top - estimatedHeight - 8;
11247
11284
  }
11248
11285
  return bottomPosition;
@@ -11250,9 +11287,9 @@ class TooltipContainerComponent {
11250
11287
  case 'left':
11251
11288
  case 'right': {
11252
11289
  const centerPosition = rect.top + rect.height / 2 - estimatedHeight / 2;
11253
- // Keep within viewport
11254
- const maxTop = viewportHeight - estimatedHeight - 10;
11255
- return Math.max(10, Math.min(centerPosition, maxTop));
11290
+ // Keep within bounds
11291
+ const maxTop = bottomBound - estimatedHeight;
11292
+ return Math.max(topBound, Math.min(centerPosition, maxTop));
11256
11293
  }
11257
11294
  default:
11258
11295
  return rect.top - estimatedHeight - 8;
@@ -23866,18 +23903,7 @@ class SymphiqFunnelAnalysisPreviewComponent {
23866
23903
  return getTrendClasses(trend, this.viewMode());
23867
23904
  }
23868
23905
  getMetricLabel(metric) {
23869
- const labels = {
23870
- 'SESSIONS': 'Sessions',
23871
- 'ACTIVE_USERS': 'Users',
23872
- 'BOUNCE_RATE': 'Bounce Rate',
23873
- 'PURCHASES': 'Purchases',
23874
- 'ECOMMERCE_PURCHASES': 'Purchases',
23875
- 'ADD_TO_CARTS': 'Add to Cart',
23876
- 'CHECKOUTS': 'Checkouts',
23877
- 'ECOMMERCE_CONVERSION_RATE': 'Conv Rate',
23878
- 'AVERAGE_ORDER_VALUE': 'AOV'
23879
- };
23880
- return labels[metric.metric || ''] || metric.metric || 'Metric';
23906
+ return MetricEnumUtil.title(metric.metric) || metric.metric || 'Metric';
23881
23907
  }
23882
23908
  formatMetricValue(metric) {
23883
23909
  const value = metric.currentValue || 0;
@@ -24222,7 +24248,7 @@ class SymphiqFunnelAnalysisPreviewComponent {
24222
24248
  <symphiq-tooltip-container />
24223
24249
  `, styles: ["@keyframes statusPulse{0%,to{opacity:1}50%{opacity:.6}}.status-pulse{animation:statusPulse 2s ease-in-out infinite}\n"] }]
24224
24250
  }], () => [], { analysisInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "funnelAnalysis", required: false }] }], viewMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewMode", required: false }] }], useSampleData: [{ type: i0.Input, args: [{ isSignal: true, alias: "useSampleData", required: false }] }], scrollElement: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollElement", required: false }] }], onViewAnalysis: [{ type: i0.Output, args: ["onViewAnalysis"] }] }); })();
24225
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SymphiqFunnelAnalysisPreviewComponent, { className: "SymphiqFunnelAnalysisPreviewComponent", filePath: "lib/components/funnel-analysis-preview/symphiq-funnel-analysis-preview.component.ts", lineNumber: 216 }); })();
24251
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SymphiqFunnelAnalysisPreviewComponent, { className: "SymphiqFunnelAnalysisPreviewComponent", filePath: "lib/components/funnel-analysis-preview/symphiq-funnel-analysis-preview.component.ts", lineNumber: 218 }); })();
24226
24252
 
24227
24253
  const _c0$3 = ["chartdiv"];
24228
24254
  class LineChartComponent {