@gemx-dev/heatmap-react 3.5.35 → 3.5.38

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.
Files changed (79) hide show
  1. package/dist/esm/components/Layout/ContentToolbar.d.ts.map +1 -1
  2. package/dist/esm/components/Layout/ContentTopBar.d.ts.map +1 -1
  3. package/dist/esm/components/Layout/LeftSidebar.d.ts.map +1 -1
  4. package/dist/esm/components/VizDom/VizDomContainer.d.ts.map +1 -1
  5. package/dist/esm/configs/iframe.d.ts +1 -1
  6. package/dist/esm/configs/iframe.d.ts.map +1 -1
  7. package/dist/esm/helpers/index.d.ts +2 -0
  8. package/dist/esm/helpers/index.d.ts.map +1 -1
  9. package/dist/esm/helpers/viewport-fixer.d.ts +13 -0
  10. package/dist/esm/helpers/viewport-fixer.d.ts.map +1 -0
  11. package/dist/esm/helpers/viewport-replacer.d.ts +26 -0
  12. package/dist/esm/helpers/viewport-replacer.d.ts.map +1 -0
  13. package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
  14. package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts +1 -1
  15. package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts.map +1 -1
  16. package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
  17. package/dist/esm/hooks/viz-scale/useIframeHeight.d.ts +0 -1
  18. package/dist/esm/hooks/viz-scale/useIframeHeight.d.ts.map +1 -1
  19. package/dist/esm/hooks/viz-scale/useScrollSync.d.ts.map +1 -1
  20. package/dist/esm/index.d.ts +1 -1
  21. package/dist/esm/index.d.ts.map +1 -1
  22. package/dist/esm/index.js +349 -33
  23. package/dist/esm/index.mjs +349 -33
  24. package/dist/esm/stores/config.d.ts +7 -0
  25. package/dist/esm/stores/config.d.ts.map +1 -0
  26. package/dist/esm/stores/index.d.ts +2 -0
  27. package/dist/esm/stores/index.d.ts.map +1 -1
  28. package/dist/esm/stores/mode-live.d.ts +6 -0
  29. package/dist/esm/stores/mode-live.d.ts.map +1 -0
  30. package/dist/esm/stores/viz.d.ts +2 -0
  31. package/dist/esm/stores/viz.d.ts.map +1 -1
  32. package/dist/esm/types/control.d.ts +1 -1
  33. package/dist/esm/types/control.d.ts.map +1 -1
  34. package/dist/esm/types/index.d.ts +1 -0
  35. package/dist/esm/types/index.d.ts.map +1 -1
  36. package/dist/esm/types/viewport-fixer.d.ts +31 -0
  37. package/dist/esm/types/viewport-fixer.d.ts.map +1 -0
  38. package/dist/esm/ui/BoxStack/BoxStack.d.ts +1 -1
  39. package/dist/esm/ui/BoxStack/BoxStack.d.ts.map +1 -1
  40. package/dist/style.css +45 -1
  41. package/dist/umd/components/Layout/ContentToolbar.d.ts.map +1 -1
  42. package/dist/umd/components/Layout/ContentTopBar.d.ts.map +1 -1
  43. package/dist/umd/components/Layout/LeftSidebar.d.ts.map +1 -1
  44. package/dist/umd/components/VizDom/VizDomContainer.d.ts.map +1 -1
  45. package/dist/umd/configs/iframe.d.ts +1 -1
  46. package/dist/umd/configs/iframe.d.ts.map +1 -1
  47. package/dist/umd/helpers/index.d.ts +2 -0
  48. package/dist/umd/helpers/index.d.ts.map +1 -1
  49. package/dist/umd/helpers/viewport-fixer.d.ts +13 -0
  50. package/dist/umd/helpers/viewport-fixer.d.ts.map +1 -0
  51. package/dist/umd/helpers/viewport-replacer.d.ts +26 -0
  52. package/dist/umd/helpers/viewport-replacer.d.ts.map +1 -0
  53. package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
  54. package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts +1 -1
  55. package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts.map +1 -1
  56. package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
  57. package/dist/umd/hooks/viz-scale/useIframeHeight.d.ts +0 -1
  58. package/dist/umd/hooks/viz-scale/useIframeHeight.d.ts.map +1 -1
  59. package/dist/umd/hooks/viz-scale/useScrollSync.d.ts.map +1 -1
  60. package/dist/umd/index.d.ts +1 -1
  61. package/dist/umd/index.d.ts.map +1 -1
  62. package/dist/umd/index.js +2 -2
  63. package/dist/umd/stores/config.d.ts +7 -0
  64. package/dist/umd/stores/config.d.ts.map +1 -0
  65. package/dist/umd/stores/index.d.ts +2 -0
  66. package/dist/umd/stores/index.d.ts.map +1 -1
  67. package/dist/umd/stores/mode-live.d.ts +6 -0
  68. package/dist/umd/stores/mode-live.d.ts.map +1 -0
  69. package/dist/umd/stores/viz.d.ts +2 -0
  70. package/dist/umd/stores/viz.d.ts.map +1 -1
  71. package/dist/umd/types/control.d.ts +1 -1
  72. package/dist/umd/types/control.d.ts.map +1 -1
  73. package/dist/umd/types/index.d.ts +1 -0
  74. package/dist/umd/types/index.d.ts.map +1 -1
  75. package/dist/umd/types/viewport-fixer.d.ts +31 -0
  76. package/dist/umd/types/viewport-fixer.d.ts.map +1 -0
  77. package/dist/umd/ui/BoxStack/BoxStack.d.ts +1 -1
  78. package/dist/umd/ui/BoxStack/BoxStack.d.ts.map +1 -1
  79. package/package.json +1 -1
package/dist/esm/index.js CHANGED
@@ -85,6 +85,14 @@ const useHeatmapControlStore = create()((set, get) => {
85
85
  };
86
86
  });
87
87
 
88
+ const useHeatmapConfigStore = create()((set, get) => {
89
+ return {
90
+ mode: 'single',
91
+ setMode: (mode) => set({ mode }),
92
+ resetMode: () => set({ mode: 'single' }),
93
+ };
94
+ });
95
+
88
96
  var IHeatmapType;
89
97
  (function (IHeatmapType) {
90
98
  IHeatmapType["Click"] = "click";
@@ -128,6 +136,8 @@ const useHeatmapInteractionStore = create()((set, get) => {
128
136
 
129
137
  const useHeatmapVizStore = create()((set, get) => {
130
138
  return {
139
+ isRenderViz: false,
140
+ setIsRenderViz: (isRenderViz) => set({ isRenderViz }),
131
141
  scale: 1,
132
142
  vizRef: undefined,
133
143
  iframeHeight: 0,
@@ -137,6 +147,13 @@ const useHeatmapVizStore = create()((set, get) => {
137
147
  };
138
148
  });
139
149
 
150
+ const useHeatmapLiveStore = create()((set, get) => {
151
+ return {
152
+ htmlContent: '',
153
+ setHtmlContent: (htmlContent) => set({ htmlContent }),
154
+ };
155
+ });
156
+
140
157
  const useRegisterConfig = () => {
141
158
  const config = useHeatmapDataStore((state) => state.config);
142
159
  const setIsRendering = useHeatmapDataStore((state) => state.setIsRendering);
@@ -436,6 +453,266 @@ function isElementInViewport(elementRect, visualRef, scale) {
436
453
  return elementBottom > viewportTop && elementTop < viewportBottom;
437
454
  }
438
455
 
456
+ const getScriptInjectCode = async () => {
457
+ const moduleResult = (await Promise.resolve().then(function () { return viewportReplacer; }));
458
+ const ActualClass = moduleResult.default;
459
+ const classCode = ActualClass.toString();
460
+ const classInstantiateCode = ActualClass.name;
461
+ const scriptCode = `
462
+ (function() {
463
+ 'use strict';
464
+ ${classCode}
465
+ new ${classInstantiateCode}()
466
+ })();
467
+ `;
468
+ return scriptCode;
469
+ };
470
+ class ViewportUnitsFixer {
471
+ iframe = null;
472
+ config;
473
+ constructor(config) {
474
+ this.config = config;
475
+ this.iframe = config.iframe;
476
+ this.init();
477
+ }
478
+ async init() {
479
+ if (!this.iframe) {
480
+ console.error('[Parent] Required elements not found');
481
+ return;
482
+ }
483
+ // this.injectScriptContent = await generateIframeInjectScript();
484
+ window.addEventListener('message', this.handleMessage.bind(this));
485
+ if (this.iframe.contentDocument?.readyState === 'complete') {
486
+ await this.injectScript();
487
+ }
488
+ else {
489
+ this.iframe.addEventListener('load', () => this.injectScript());
490
+ }
491
+ }
492
+ async injectScript() {
493
+ if (!this.iframe?.contentWindow || !this.iframe.contentDocument)
494
+ return;
495
+ const win = this.iframe.contentWindow;
496
+ const doc = this.iframe.contentDocument;
497
+ win.__viewportConfig = this.config;
498
+ const script = doc.createElement('script');
499
+ const codeInject = await getScriptInjectCode();
500
+ script.textContent = codeInject;
501
+ script.type = 'text/javascript';
502
+ script.id = 'viewport-replacer';
503
+ script.onload = () => console.log('[Parent] Viewport replacer module loaded');
504
+ script.onerror = () => {
505
+ this.config.onSuccess?.({
506
+ height: 1000,
507
+ });
508
+ };
509
+ doc.head.appendChild(script);
510
+ }
511
+ handleMessage(event) {
512
+ const data = event.data;
513
+ if (!data || data.type !== 'IFRAME_HEIGHT_CALCULATED')
514
+ return;
515
+ this.config.onSuccess?.(data);
516
+ }
517
+ recalculate() {
518
+ this.injectScript();
519
+ }
520
+ }
521
+ function initViewportFixer(config) {
522
+ const fixer = new ViewportUnitsFixer(config);
523
+ window.viewportFixer = fixer;
524
+ window.addEventListener('iframe-dimensions-applied', ((e) => {
525
+ const ev = e;
526
+ console.log('Iframe dimensions finalized:', ev.detail);
527
+ }));
528
+ return fixer;
529
+ }
530
+
531
+ class ViewportUnitsReplacer {
532
+ config;
533
+ regex = /([-.\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/gi;
534
+ constructor() {
535
+ if (!window.__viewportConfig) {
536
+ throw new Error('[Iframe] Do not have viewport config');
537
+ }
538
+ this.config = window.__viewportConfig;
539
+ console.log('[Iframe] ViewportUnitsReplacer started with config:', this.config);
540
+ this.init();
541
+ }
542
+ px(value) {
543
+ return `${value.toFixed(2)}px`;
544
+ }
545
+ convert(value, unit) {
546
+ const num = parseFloat(value);
547
+ if (isNaN(num))
548
+ return value;
549
+ const map = {
550
+ vh: this.config.targetHeight,
551
+ svh: this.config.targetHeight,
552
+ lvh: this.config.targetHeight,
553
+ dvh: this.config.targetHeight,
554
+ vw: this.config.targetWidth,
555
+ svw: this.config.targetWidth,
556
+ lvw: this.config.targetWidth,
557
+ dvw: this.config.targetWidth,
558
+ };
559
+ return this.px((num / 100) * (map[unit.toLowerCase()] || 0));
560
+ }
561
+ replaceInText(cssText) {
562
+ return cssText.replace(this.regex, (_, value, unit) => this.convert(value, unit));
563
+ }
564
+ processInlineStyles() {
565
+ let count = 0;
566
+ document.querySelectorAll('[style]').forEach((el) => {
567
+ const style = el.getAttribute('style');
568
+ if (style && this.regex.test(style)) {
569
+ el.setAttribute('style', this.replaceInText(style));
570
+ count++;
571
+ }
572
+ });
573
+ console.log(`[Iframe] Replaced ${count} inline style elements`);
574
+ return count;
575
+ }
576
+ processStyleTags() {
577
+ let count = 0;
578
+ document.querySelectorAll('style').forEach((tag) => {
579
+ const css = tag.textContent || '';
580
+ if (this.regex.test(css)) {
581
+ tag.textContent = this.replaceInText(css);
582
+ count++;
583
+ }
584
+ });
585
+ console.log(`[Iframe] Replaced ${count} <style> tags`);
586
+ return count;
587
+ }
588
+ processRule(rule) {
589
+ let count = 0;
590
+ if ('style' in rule && rule.style) {
591
+ const style = rule.style;
592
+ for (let i = 0; i < style.length; i++) {
593
+ const prop = style[i];
594
+ const value = style.getPropertyValue(prop);
595
+ if (value && this.regex.test(value)) {
596
+ style.setProperty(prop, this.replaceInText(value), style.getPropertyPriority(prop));
597
+ count++;
598
+ }
599
+ }
600
+ }
601
+ if ('cssRules' in rule) {
602
+ const rules = rule;
603
+ for (const r of Array.from(rules.cssRules || [])) {
604
+ count += this.processRule(r);
605
+ }
606
+ }
607
+ return count;
608
+ }
609
+ processStylesheets() {
610
+ let total = 0;
611
+ Array.from(document.styleSheets).forEach((sheet) => {
612
+ try {
613
+ // Bỏ qua external CSS (cross-origin)
614
+ if (sheet.href && !sheet.href.startsWith(location.origin)) {
615
+ console.log('[Iframe] Skipping external CSS:', sheet.href);
616
+ return;
617
+ }
618
+ const rules = sheet.cssRules || sheet.rules;
619
+ if (!rules)
620
+ return;
621
+ for (const rule of Array.from(rules)) {
622
+ total += this.processRule(rule);
623
+ }
624
+ }
625
+ catch (e) {
626
+ console.warn('[Iframe] Cannot read stylesheet (CORS?):', e.message);
627
+ }
628
+ });
629
+ console.log(`[Iframe] Replaced ${total} rules in stylesheets`);
630
+ return total;
631
+ }
632
+ async processLinkedStylesheets() {
633
+ const links = document.querySelectorAll('link[rel="stylesheet"]');
634
+ let count = 0;
635
+ for (const link of Array.from(links)) {
636
+ if (!link.href.startsWith(location.origin)) {
637
+ console.log('[Iframe] Skipping external CSS:', link.href);
638
+ continue;
639
+ }
640
+ try {
641
+ const res = await fetch(link.href);
642
+ let css = await res.text();
643
+ if (this.regex.test(css)) {
644
+ css = this.replaceInText(css);
645
+ const style = document.createElement('style');
646
+ style.textContent = css;
647
+ style.dataset.originalHref = link.href;
648
+ link.parentNode?.insertBefore(style, link);
649
+ link.remove();
650
+ count++;
651
+ }
652
+ }
653
+ catch (e) {
654
+ console.warn('[Iframe] Cannot load CSS:', link.href, e);
655
+ }
656
+ }
657
+ console.log(`[Iframe] Replaced ${count} linked CSS files`);
658
+ return count;
659
+ }
660
+ getFinalHeight() {
661
+ // Trigger reflow
662
+ void document.body.offsetHeight;
663
+ return Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight, document.documentElement.clientHeight);
664
+ }
665
+ notifyParent(height) {
666
+ window.parent.postMessage({
667
+ type: 'IFRAME_HEIGHT_CALCULATED',
668
+ height,
669
+ width: document.body.scrollWidth,
670
+ }, '*');
671
+ console.log('[Iframe] Sent height to parent:', height);
672
+ }
673
+ async waitForResources() {
674
+ if ('fonts' in document) {
675
+ await document.fonts.ready;
676
+ }
677
+ const images = Array.from(document.images).filter((img) => !img.complete);
678
+ if (images.length > 0) {
679
+ await Promise.all(images.map((img) => new Promise((resolve) => {
680
+ img.onload = img.onerror = resolve;
681
+ })));
682
+ }
683
+ }
684
+ async run() {
685
+ try {
686
+ this.processInlineStyles();
687
+ this.processStyleTags();
688
+ this.processStylesheets();
689
+ await this.processLinkedStylesheets();
690
+ // await this.waitForResources();
691
+ requestAnimationFrame(() => {
692
+ const height = this.getFinalHeight();
693
+ this.notifyParent(height);
694
+ });
695
+ }
696
+ catch (err) {
697
+ console.error('[Iframe] Critical error:', err);
698
+ this.notifyParent(document.body.scrollHeight || 1000);
699
+ }
700
+ }
701
+ init() {
702
+ if (document.readyState === 'loading') {
703
+ document.addEventListener('DOMContentLoaded', () => this.run());
704
+ }
705
+ else {
706
+ this.run();
707
+ }
708
+ }
709
+ }
710
+
711
+ var viewportReplacer = /*#__PURE__*/Object.freeze({
712
+ __proto__: null,
713
+ default: ViewportUnitsReplacer
714
+ });
715
+
439
716
  const scrollToElementIfNeeded = (visualRef, rect, scale) => {
440
717
  if (!visualRef.current)
441
718
  return;
@@ -691,24 +968,22 @@ const useHeatmapRender = () => {
691
968
  const data = useHeatmapDataStore((state) => state.data);
692
969
  const config = useHeatmapDataStore((state) => state.config);
693
970
  const setVizRef = useHeatmapVizStore((state) => state.setVizRef);
971
+ const setIsRenderViz = useHeatmapVizStore((state) => state.setIsRenderViz);
972
+ const setIframeHeight = useHeatmapVizStore((state) => state.setIframeHeight);
694
973
  const iframeRef = useRef(null);
695
974
  const renderHeatmap = useCallback(async (payloads) => {
696
975
  if (!payloads || payloads.length === 0)
697
976
  return;
977
+ setIsRenderViz(false);
698
978
  const visualizer = new Visualizer();
699
979
  const iframe = iframeRef.current;
700
980
  if (!iframe?.contentWindow)
701
981
  return;
702
982
  await visualizer.html(payloads, iframe.contentWindow);
703
- // iframe.contentDocument?.body.querySelectorAll('body>*').forEach((element) => {
704
- // console.log(`🚀 🐥 ~ useHeatmapRender ~ element:`, element);
705
- // const isClosedEcomsendWidget = element.closest('#ecomsend-widget');
706
- // const isEcomsendWidget = element.querySelector('#ecomsend-widget');
707
- // const isRemove = !(isClosedEcomsendWidget || isEcomsendWidget);
708
- // if (isRemove) {
709
- // element.remove();
710
- // }
711
- // });
983
+ reset(iframe, payloads, (height) => {
984
+ height && setIframeHeight(height);
985
+ setIsRenderViz(true);
986
+ });
712
987
  setVizRef(visualizer);
713
988
  }, []);
714
989
  useEffect(() => {
@@ -723,6 +998,50 @@ const useHeatmapRender = () => {
723
998
  iframeRef,
724
999
  };
725
1000
  };
1001
+ function sort(a, b) {
1002
+ return a.time - b.time;
1003
+ }
1004
+ function findLastSizeOfDom(data) {
1005
+ const firstDoc = data.find((item) => item.envelope.sequence === 1)?.doc;
1006
+ const docSorted = firstDoc?.sort(sort);
1007
+ const firstEvent = docSorted?.[0];
1008
+ const docSize = {
1009
+ width: firstEvent?.data.width,
1010
+ height: firstEvent?.data.height,
1011
+ };
1012
+ const newData = JSON.parse(JSON.stringify(data));
1013
+ const reversedData = newData.reverse();
1014
+ const lastResizeEvent = reversedData.find((item) => !!item.resize);
1015
+ const firstEventResize = lastResizeEvent?.resize?.[0];
1016
+ const resize = {
1017
+ width: firstEventResize?.data.width,
1018
+ height: firstEventResize?.data.height,
1019
+ };
1020
+ return {
1021
+ doc: docSize,
1022
+ resize: resize,
1023
+ size: {
1024
+ width: resize.width ?? docSize.width,
1025
+ height: resize.height ?? docSize.height,
1026
+ },
1027
+ };
1028
+ }
1029
+ function reset(iframe, payloads, onSuccess) {
1030
+ const { size } = findLastSizeOfDom(payloads);
1031
+ const docWidth = size.width ?? 0;
1032
+ const docHeight = size.height ?? 0;
1033
+ const viewportFixer = initViewportFixer({
1034
+ targetWidth: docWidth,
1035
+ targetHeight: docHeight,
1036
+ iframe: iframe,
1037
+ onSuccess: (data) => {
1038
+ onSuccess(data.height);
1039
+ iframe.height = `${data.height}px`;
1040
+ },
1041
+ });
1042
+ viewportFixer.recalculate();
1043
+ return iframe;
1044
+ }
726
1045
 
727
1046
  function isMobileDevice(userAgent) {
728
1047
  if (!userAgent)
@@ -894,24 +1213,24 @@ const useContainerDimensions = (props) => {
894
1213
  return { containerWidth, containerHeight };
895
1214
  };
896
1215
 
897
- const useContentDimensions = (props) => {
898
- const contentWidth = useHeatmapDataStore((state) => state.config?.width ?? 0);
899
- const { iframeRef } = props;
1216
+ const useContentDimensions = ({ iframeRef, }) => {
1217
+ const config = useHeatmapDataStore((state) => state.config);
1218
+ const contentWidth = config?.width ?? 0;
900
1219
  useEffect(() => {
901
1220
  if (!contentWidth)
902
1221
  return;
903
1222
  if (!iframeRef.current)
904
1223
  return;
905
- iframeRef.current.width = `${contentWidth}px`;
1224
+ // iframeRef.current.width = `${contentWidth}px`;
906
1225
  }, [contentWidth, iframeRef]);
907
1226
  return { contentWidth };
908
1227
  };
909
1228
 
910
- // Hook 3: Iframe Height Observer
911
1229
  const useIframeHeight = (props) => {
912
- const { iframeRef, contentWidth } = props;
1230
+ const { iframeRef } = props;
913
1231
  const iframeHeight = useHeatmapVizStore((state) => state.iframeHeight);
914
1232
  const setIframeHeight = useHeatmapVizStore((state) => state.setIframeHeight);
1233
+ const isRenderViz = useHeatmapVizStore((state) => state.isRenderViz);
915
1234
  const resizeObserverRef = useRef(null);
916
1235
  const mutationObserverRef = useRef(null);
917
1236
  const updateIframeHeight = useCallback(() => {
@@ -933,17 +1252,9 @@ const useIframeHeight = (props) => {
933
1252
  console.warn('Cannot measure iframe content:', error);
934
1253
  }
935
1254
  }, [iframeRef, setIframeHeight]);
936
- useEffect(() => {
937
- if (contentWidth > 0) {
938
- const timeoutId = setTimeout(() => {
939
- updateIframeHeight();
940
- }, 100);
941
- return () => clearTimeout(timeoutId);
942
- }
943
- }, [contentWidth]);
944
1255
  useEffect(() => {
945
1256
  const iframe = iframeRef.current;
946
- if (!iframe)
1257
+ if (!iframe || !isRenderViz)
947
1258
  return;
948
1259
  const setupObservers = () => {
949
1260
  try {
@@ -995,7 +1306,7 @@ const useIframeHeight = (props) => {
995
1306
  }
996
1307
  iframe.removeEventListener('load', setupObservers);
997
1308
  };
998
- }, [iframeRef, updateIframeHeight]);
1309
+ }, [iframeRef, isRenderViz, updateIframeHeight]);
999
1310
  return { iframeHeight };
1000
1311
  };
1001
1312
 
@@ -1025,7 +1336,6 @@ const useScrollSync = ({ iframeRef }) => {
1025
1336
  if (iframeWindow && iframeDocument) {
1026
1337
  const iframeScrollTop = scrollTop / widthScale;
1027
1338
  iframe.style.top = `${iframeScrollTop}px`;
1028
- // iframeWindow.scrollTo({ top: iframeScrollTop, behavior: 'smooth' });
1029
1339
  }
1030
1340
  }
1031
1341
  catch (error) {
@@ -1036,13 +1346,14 @@ const useScrollSync = ({ iframeRef }) => {
1036
1346
  };
1037
1347
 
1038
1348
  const useHeatmapScale = (props) => {
1349
+ // const iframeHeight = useHeatmapVizStore((state) => state.iframeHeight);
1039
1350
  const { wrapperRef, iframeRef, visualRef } = props;
1040
1351
  // 1. Observe container dimensions
1041
1352
  const { containerWidth, containerHeight } = useContainerDimensions({ wrapperRef });
1042
1353
  // 2. Get content dimensions from config
1043
1354
  const { contentWidth } = useContentDimensions({ iframeRef });
1044
1355
  // 3. Observe iframe height (now reacts to width changes)
1045
- const { iframeHeight } = useIframeHeight({ iframeRef, contentWidth });
1356
+ const { iframeHeight } = useIframeHeight({ iframeRef });
1046
1357
  // 4. Calculate scale
1047
1358
  const { scale } = useScaleCalculation({ containerWidth, contentWidth });
1048
1359
  // 5. Setup scroll sync
@@ -1068,7 +1379,8 @@ const BoxStack = forwardRef(({ children, ...props }, ref) => {
1068
1379
  const style = props.style || {};
1069
1380
  const gap = props.gap || 0;
1070
1381
  const height = props.height || 'auto';
1071
- const zIndex = props.zIndex || 0;
1382
+ const isZIndexDefined = typeof props.zIndex !== undefined;
1383
+ const zIndex = props.zIndex;
1072
1384
  const backgroundColor = props.backgroundColor || 'transparent';
1073
1385
  const styleGap = useMemo(() => {
1074
1386
  switch (flexDirection) {
@@ -1091,8 +1403,8 @@ const BoxStack = forwardRef(({ children, ...props }, ref) => {
1091
1403
  justifyContent,
1092
1404
  alignItems,
1093
1405
  height,
1094
- zIndex,
1095
1406
  backgroundColor,
1407
+ ...(isZIndexDefined ? { zIndex } : {}),
1096
1408
  ...styleGap,
1097
1409
  ...style,
1098
1410
  };
@@ -1101,9 +1413,10 @@ const BoxStack = forwardRef(({ children, ...props }, ref) => {
1101
1413
 
1102
1414
  const ContentTopBar = () => {
1103
1415
  const controls = useHeatmapControlStore((state) => state.controls);
1416
+ const TopBar = controls.TopBar;
1104
1417
  return (jsx(BoxStack, { id: "gx-hm-content-header", flexDirection: "row", alignItems: "center", overflow: "auto", zIndex: 1, backgroundColor: "white", style: {
1105
1418
  borderBottom: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
1106
- }, children: controls.TopBar ?? null }));
1419
+ }, children: TopBar && jsx(TopBar, {}) }));
1107
1420
  };
1108
1421
 
1109
1422
  const useClickmap = () => {
@@ -1414,11 +1727,12 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
1414
1727
  const VizDomContainer = () => {
1415
1728
  const controls = useHeatmapControlStore((state) => state.controls);
1416
1729
  const isRendering = useHeatmapDataStore((state) => state.isRendering);
1730
+ const iframeHeight = useHeatmapVizStore((state) => state.iframeHeight);
1417
1731
  if (isRendering)
1418
1732
  return controls.VizLoading ?? null;
1419
- return (jsx(BoxStack, { id: "gx-hm-viz-container", flexDirection: "column", flex: "1 1 auto", overflow: "auto", children: jsx(BoxStack, { id: "gx-hm-content", flexDirection: "column", flex: "1 1 auto", overflow: "hidden", style: {
1733
+ return (jsx(BoxStack, { id: "gx-hm-viz-container", flexDirection: "column", flex: "1 1 auto", overflow: "auto", zIndex: 1, children: jsxs(BoxStack, { id: "gx-hm-content", flexDirection: "column", flex: "1 1 auto", overflow: "hidden", style: {
1420
1734
  minWidth: '394px',
1421
- }, children: jsx(VizDomRenderer, {}) }) }));
1735
+ }, children: [jsx(VizDomRenderer, {}), iframeHeight === 0 && (jsxs("div", { className: "gx-hm-loading", children: [jsx("div", { className: "gx-hm-loading--spinner" }), jsx("p", { className: "gx-hm-loading--text", children: "Loading visualization..." })] }))] }) }));
1422
1736
  };
1423
1737
 
1424
1738
  const ContentMetricBar = () => {
@@ -1431,6 +1745,7 @@ const ContentMetricBar = () => {
1431
1745
  const ContentToolbar = () => {
1432
1746
  const controls = useHeatmapControlStore((state) => state.controls);
1433
1747
  return (jsx("div", { id: "gx-hm-content-toolbar", style: {
1748
+ zIndex: 2,
1434
1749
  position: 'absolute',
1435
1750
  bottom: 0,
1436
1751
  left: '8px',
@@ -1451,6 +1766,7 @@ const LeftSidebar = () => {
1451
1766
  return (jsx("div", { className: "gx-hm-sidebar", style: {
1452
1767
  height: '100%',
1453
1768
  display: 'flex',
1769
+ zIndex: 1,
1454
1770
  ...(isHideSidebar
1455
1771
  ? {
1456
1772
  width: '0',
@@ -1509,4 +1825,4 @@ var ErrorType;
1509
1825
  ErrorType["DataError"] = "DataError";
1510
1826
  })(ErrorType || (ErrorType = {}));
1511
1827
 
1512
- export { GraphView, HeatmapLayout, IHeatmapType, useHeatmapDataStore, useHeatmapInteractionStore };
1828
+ export { GraphView, HeatmapLayout, IHeatmapType, useHeatmapConfigStore, useHeatmapDataStore, useHeatmapInteractionStore, useHeatmapLiveStore };