@gemx-dev/heatmap-react 3.5.42 → 3.5.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/esm/components/VizLive/VizLiveHeatmap.d.ts.map +1 -1
  2. package/dist/esm/helpers/iframe-helper/fixer.d.ts +18 -0
  3. package/dist/esm/helpers/iframe-helper/fixer.d.ts.map +1 -0
  4. package/dist/esm/helpers/iframe-helper/index.d.ts +2 -0
  5. package/dist/esm/helpers/iframe-helper/index.d.ts.map +1 -0
  6. package/dist/esm/helpers/iframe-helper/init.d.ts +5 -0
  7. package/dist/esm/helpers/iframe-helper/init.d.ts.map +1 -0
  8. package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts +28 -0
  9. package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -0
  10. package/dist/esm/helpers/iframe-helper/navigation-blocker.d.ts +20 -0
  11. package/dist/esm/helpers/iframe-helper/navigation-blocker.d.ts.map +1 -0
  12. package/dist/esm/helpers/iframe-helper/style-replacer.d.ts +25 -0
  13. package/dist/esm/helpers/iframe-helper/style-replacer.d.ts.map +1 -0
  14. package/dist/esm/helpers/index.d.ts +1 -2
  15. package/dist/esm/helpers/index.d.ts.map +1 -1
  16. package/dist/esm/index.js +447 -129
  17. package/dist/esm/index.mjs +447 -129
  18. package/dist/esm/types/iframe-helper.d.ts +20 -0
  19. package/dist/esm/types/iframe-helper.d.ts.map +1 -0
  20. package/dist/esm/types/index.d.ts +1 -1
  21. package/dist/esm/types/index.d.ts.map +1 -1
  22. package/dist/umd/components/VizLive/VizLiveHeatmap.d.ts.map +1 -1
  23. package/dist/umd/helpers/iframe-helper/fixer.d.ts +18 -0
  24. package/dist/umd/helpers/iframe-helper/fixer.d.ts.map +1 -0
  25. package/dist/umd/helpers/iframe-helper/index.d.ts +2 -0
  26. package/dist/umd/helpers/iframe-helper/index.d.ts.map +1 -0
  27. package/dist/umd/helpers/iframe-helper/init.d.ts +5 -0
  28. package/dist/umd/helpers/iframe-helper/init.d.ts.map +1 -0
  29. package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts +28 -0
  30. package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -0
  31. package/dist/umd/helpers/iframe-helper/navigation-blocker.d.ts +20 -0
  32. package/dist/umd/helpers/iframe-helper/navigation-blocker.d.ts.map +1 -0
  33. package/dist/umd/helpers/iframe-helper/style-replacer.d.ts +25 -0
  34. package/dist/umd/helpers/iframe-helper/style-replacer.d.ts.map +1 -0
  35. package/dist/umd/helpers/index.d.ts +1 -2
  36. package/dist/umd/helpers/index.d.ts.map +1 -1
  37. package/dist/umd/index.js +2 -2
  38. package/dist/umd/types/iframe-helper.d.ts +20 -0
  39. package/dist/umd/types/iframe-helper.d.ts.map +1 -0
  40. package/dist/umd/types/index.d.ts +1 -1
  41. package/dist/umd/types/index.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/dist/esm/helpers/viewport-fixer.d.ts +0 -13
  44. package/dist/esm/helpers/viewport-fixer.d.ts.map +0 -1
  45. package/dist/esm/helpers/viewport-replacer.d.ts +0 -25
  46. package/dist/esm/helpers/viewport-replacer.d.ts.map +0 -1
  47. package/dist/esm/types/viewport-fixer.d.ts +0 -30
  48. package/dist/esm/types/viewport-fixer.d.ts.map +0 -1
  49. package/dist/umd/helpers/viewport-fixer.d.ts +0 -13
  50. package/dist/umd/helpers/viewport-fixer.d.ts.map +0 -1
  51. package/dist/umd/helpers/viewport-replacer.d.ts +0 -25
  52. package/dist/umd/helpers/viewport-replacer.d.ts.map +0 -1
  53. package/dist/umd/types/viewport-fixer.d.ts +0 -30
  54. package/dist/umd/types/viewport-fixer.d.ts.map +0 -1
package/dist/esm/index.js CHANGED
@@ -511,85 +511,311 @@ function isElementInViewport(elementRect, visualRef, scale) {
511
511
  return elementBottom > viewportTop && elementTop < viewportBottom;
512
512
  }
513
513
 
514
- const getScriptInjectCode = async () => {
515
- const moduleResult = (await Promise.resolve().then(function () { return viewportReplacer; }));
516
- const ActualClass = moduleResult.default;
517
- const classCode = ActualClass.toString();
518
- const classInstantiateCode = ActualClass.name;
519
- const scriptCode = `
520
- (function() {
521
- 'use strict';
522
- ${classCode}
523
- const replacer = new ${classInstantiateCode}();
524
- replacer.init();
525
- })();
526
- `;
527
- return scriptCode;
528
- };
529
- class ViewportUnitsFixer {
530
- iframe = null;
531
- config;
532
- constructor(config) {
533
- this.config = config;
534
- this.iframe = config.iframe;
514
+ class IframeNavigationBlockerV2 {
515
+ doc;
516
+ win;
517
+ isEnabled = false;
518
+ showMessage = false;
519
+ originalWindowOpen;
520
+ observers = [];
521
+ constructor(iframe) {
522
+ if (!iframe.contentDocument || !iframe.contentWindow) {
523
+ throw new Error('Iframe document or window not accessible');
524
+ }
525
+ this.doc = iframe.contentDocument;
526
+ this.win = iframe.contentWindow;
527
+ this.originalWindowOpen = this.win.open.bind(this.win);
535
528
  this.init();
536
529
  }
537
- async init() {
538
- if (!this.iframe) {
539
- console.error('[Parent] Required elements not found');
540
- return;
541
- }
542
- // this.injectScriptContent = await generateIframeInjectScript();
543
- window.addEventListener('message', this.handleMessage.bind(this));
544
- if (this.iframe.contentDocument?.readyState === 'complete') {
545
- await this.injectScript();
530
+ init() {
531
+ console.log('[NavigationBlocker] Initializing...');
532
+ try {
533
+ // Chặn navigation qua links
534
+ this.blockLinkNavigation();
535
+ // Chặn form submissions
536
+ this.blockFormSubmissions();
537
+ // Chặn window.open (này an toàn)
538
+ this.blockWindowOpen();
539
+ // Chặn beforeunload để prevent navigation
540
+ this.blockBeforeUnload();
541
+ // Monitor DOM changes để block dynamic links
542
+ this.monitorDOMChanges();
543
+ // Inject CSP nếu có thể
544
+ this.injectCSP();
546
545
  }
547
- else {
548
- this.iframe.addEventListener('load', () => this.injectScript());
546
+ catch (error) {
547
+ console.error('[NavigationBlocker] Init error:', error);
549
548
  }
550
549
  }
551
- async injectScript() {
552
- if (!this.iframe?.contentWindow || !this.iframe.contentDocument)
553
- return;
554
- const doc = this.iframe.contentDocument;
555
- const win = this.iframe.contentWindow;
550
+ blockLinkNavigation() {
551
+ // Sử dụng capture phase để chặn sớm nhất
552
+ this.doc.addEventListener('click', (e) => {
553
+ if (!this.isEnabled)
554
+ return;
555
+ const target = e.target;
556
+ const link = target.closest('a');
557
+ if (link) {
558
+ const href = link.getAttribute('href');
559
+ // Cho phép hash links và empty links
560
+ if (!href || href === '' || href === '#' || href.startsWith('#')) {
561
+ console.log('[NavigationBlocker] Allowed hash navigation:', href);
562
+ return;
563
+ }
564
+ // Chặn tất cả các loại navigation
565
+ console.log('[NavigationBlocker] Blocked link navigation to:', href);
566
+ e.preventDefault();
567
+ e.stopPropagation();
568
+ e.stopImmediatePropagation();
569
+ this.notifyBlockedNavigation(href);
570
+ }
571
+ }, true);
572
+ // Chặn cả middle click và right click "open in new tab"
573
+ this.doc.addEventListener('auxclick', (e) => {
574
+ if (!this.isEnabled)
575
+ return;
576
+ const target = e.target;
577
+ const link = target.closest('a');
578
+ if (link) {
579
+ const href = link.getAttribute('href');
580
+ if (href && !href.startsWith('#')) {
581
+ console.log('[NavigationBlocker] Blocked auxclick navigation');
582
+ e.preventDefault();
583
+ e.stopPropagation();
584
+ e.stopImmediatePropagation();
585
+ }
586
+ }
587
+ }, true);
588
+ // Disable tất cả links ngay từ đầu
589
+ this.disableAllLinks();
590
+ }
591
+ disableAllLinks() {
592
+ this.doc.querySelectorAll('a[href]').forEach((link) => {
593
+ const href = link.getAttribute('href');
594
+ if (href && !href.startsWith('#')) {
595
+ // Thêm pointer-events: none và cursor
596
+ link.style.cursor = 'not-allowed';
597
+ link.setAttribute('data-navigation-blocked', 'true');
598
+ // Remove href để browser không hiện preview
599
+ link.setAttribute('data-original-href', href);
600
+ link.removeAttribute('href');
601
+ // Hoặc giữ href nhưng disable
602
+ // link.setAttribute('onclick', 'return false');
603
+ }
604
+ });
605
+ }
606
+ blockFormSubmissions() {
607
+ this.doc.addEventListener('submit', (e) => {
608
+ if (!this.isEnabled)
609
+ return;
610
+ const form = e.target;
611
+ const action = form.getAttribute('action');
612
+ // Cho phép forms không có action
613
+ if (!action || action === '' || action === '#') {
614
+ console.log('[NavigationBlocker] Allowed same-page form');
615
+ e.preventDefault();
616
+ this.handleFormSubmit(form);
617
+ return;
618
+ }
619
+ // Chặn tất cả external submissions
620
+ console.log('[NavigationBlocker] Blocked form submission to:', action);
621
+ e.preventDefault();
622
+ e.stopPropagation();
623
+ e.stopImmediatePropagation();
624
+ this.notifyBlockedNavigation(action);
625
+ }, true);
626
+ }
627
+ blockWindowOpen() {
628
+ // Override window.open - đây là safe
629
+ this.win.open = ((...args) => {
630
+ if (!this.isEnabled) {
631
+ return this.originalWindowOpen(...args);
632
+ }
633
+ const url = args[0]?.toString() || 'popup';
634
+ console.log('[NavigationBlocker] Blocked window.open:', url);
635
+ this.notifyBlockedNavigation(url);
636
+ return null;
637
+ });
638
+ }
639
+ blockBeforeUnload() {
640
+ // Chặn unload
641
+ this.win.addEventListener('beforeunload', (e) => {
642
+ if (!this.isEnabled)
643
+ return;
644
+ console.log('[NavigationBlocker] Blocked beforeunload');
645
+ e.preventDefault();
646
+ e.returnValue = '';
647
+ return '';
648
+ }, true);
649
+ // Chặn unload
650
+ this.win.addEventListener('unload', (e) => {
651
+ if (!this.isEnabled)
652
+ return;
653
+ console.log('[NavigationBlocker] Blocked unload');
654
+ e.preventDefault();
655
+ e.stopPropagation();
656
+ }, true);
657
+ // Monitor popstate
658
+ this.win.addEventListener('popstate', (e) => {
659
+ if (!this.isEnabled)
660
+ return;
661
+ console.log('[NavigationBlocker] Blocked popstate');
662
+ e.preventDefault();
663
+ e.stopPropagation();
664
+ }, true);
665
+ }
666
+ monitorDOMChanges() {
667
+ // Monitor khi có links mới được thêm vào
668
+ const observer = new MutationObserver((mutations) => {
669
+ if (!this.isEnabled)
670
+ return;
671
+ mutations.forEach((mutation) => {
672
+ mutation.addedNodes.forEach((node) => {
673
+ if (node.nodeType === Node.ELEMENT_NODE) {
674
+ const element = node;
675
+ // Nếu là link
676
+ if (element.tagName === 'A') {
677
+ const href = element.getAttribute('href');
678
+ if (href && !href.startsWith('#')) {
679
+ element.style.cursor = 'not-allowed';
680
+ element.setAttribute('data-navigation-blocked', 'true');
681
+ element.setAttribute('data-original-href', href);
682
+ element.removeAttribute('href');
683
+ }
684
+ }
685
+ // Tìm links trong subtree
686
+ element.querySelectorAll('a[href]').forEach((link) => {
687
+ const href = link.getAttribute('href');
688
+ if (href && !href.startsWith('#')) {
689
+ link.style.cursor = 'not-allowed';
690
+ link.setAttribute('data-navigation-blocked', 'true');
691
+ link.setAttribute('data-original-href', href);
692
+ link.removeAttribute('href');
693
+ }
694
+ });
695
+ }
696
+ });
697
+ });
698
+ });
699
+ observer.observe(this.doc.body, {
700
+ childList: true,
701
+ subtree: true,
702
+ });
703
+ this.observers.push(observer);
704
+ }
705
+ injectCSP() {
706
+ // Thêm CSP meta tag nếu chưa có (optional)
556
707
  try {
557
- win.__viewportConfig = this.config;
558
- const script = doc.createElement('script');
559
- const scriptCode = await getScriptInjectCode();
560
- script.textContent = scriptCode;
561
- doc.head.appendChild(script);
708
+ const existingCSP = this.doc.querySelector('meta[http-equiv="Content-Security-Policy"]');
709
+ if (!existingCSP) {
710
+ const meta = this.doc.createElement('meta');
711
+ meta.httpEquiv = 'Content-Security-Policy';
712
+ meta.content = "navigate-to 'none'"; // Chặn tất cả navigation
713
+ this.doc.head.appendChild(meta);
714
+ console.log('[NavigationBlocker] Injected CSP');
715
+ }
562
716
  }
563
717
  catch (error) {
564
- console.error('[Parent] Failed to inject script', error);
718
+ console.warn('[NavigationBlocker] Could not inject CSP:', error);
719
+ }
720
+ }
721
+ handleFormSubmit(form) {
722
+ const formData = new FormData(form);
723
+ const data = {};
724
+ formData.forEach((value, key) => {
725
+ data[key] = value;
726
+ });
727
+ console.log('[NavigationBlocker] Handling form data:', data);
728
+ window.dispatchEvent(new CustomEvent('iframe-form-submit', {
729
+ detail: { form, data },
730
+ }));
731
+ }
732
+ notifyBlockedNavigation(url) {
733
+ console.warn('[NavigationBlocker] Navigation blocked to:', url);
734
+ window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', {
735
+ detail: { url, timestamp: Date.now() },
736
+ }));
737
+ if (this.shouldShowMessage(url)) {
738
+ this.showBlockedMessage(url);
565
739
  }
566
740
  }
567
- handleMessage(event) {
568
- const data = event.data;
569
- if (!data || data.type !== 'IFRAME_HEIGHT_CALCULATED')
741
+ shouldShowMessage(url) {
742
+ return !url.startsWith('#') && url !== 'reload' && url !== 'popup';
743
+ }
744
+ showBlockedMessage(url) {
745
+ if (!this.showMessage)
570
746
  return;
571
- this.config.onSuccess?.(data);
747
+ const message = this.doc.createElement('div');
748
+ message.style.cssText = `
749
+ position: fixed;
750
+ top: 20px;
751
+ right: 20px;
752
+ background: #ff6b6b;
753
+ color: white;
754
+ padding: 12px 20px;
755
+ border-radius: 8px;
756
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
757
+ z-index: 999999;
758
+ font-family: system-ui, -apple-system, sans-serif;
759
+ font-size: 14px;
760
+ max-width: 300px;
761
+ word-break: break-word;
762
+ pointer-events: none;
763
+ `;
764
+ const shortUrl = url.length > 50 ? url.substring(0, 47) + '...' : url;
765
+ message.innerHTML = `
766
+ <div style="font-weight: 600; margin-bottom: 4px;">🚫 Navigation Blocked</div>
767
+ <div style="font-size: 12px; opacity: 0.9;">${this.escapeHtml(shortUrl)}</div>
768
+ `;
769
+ this.doc.body.appendChild(message);
770
+ setTimeout(() => {
771
+ message.style.opacity = '0';
772
+ message.style.transition = 'opacity 0.3s';
773
+ setTimeout(() => message.remove(), 300);
774
+ }, 3000);
572
775
  }
573
- recalculate() {
574
- this.injectScript();
776
+ escapeHtml(text) {
777
+ const div = this.doc.createElement('div');
778
+ div.textContent = text;
779
+ return div.innerHTML;
780
+ }
781
+ enable() {
782
+ this.isEnabled = true;
783
+ console.log('[NavigationBlocker] Enabled');
784
+ }
785
+ enableMessage() {
786
+ this.showMessage = true;
787
+ console.log('[NavigationBlocker] Enabled message');
788
+ }
789
+ disable() {
790
+ this.isEnabled = false;
791
+ console.log('[NavigationBlocker] Disabled');
792
+ }
793
+ disableMessage() {
794
+ this.showMessage = false;
795
+ console.log('[NavigationBlocker] Disabled message');
796
+ }
797
+ destroy() {
798
+ this.isEnabled = false;
799
+ this.showMessage = false;
800
+ // Cleanup observers
801
+ this.observers.forEach((observer) => observer.disconnect());
802
+ this.observers = [];
803
+ console.log('[NavigationBlocker] Destroyed');
575
804
  }
576
- }
577
- function initViewportFixer(config) {
578
- const fixer = new ViewportUnitsFixer(config);
579
- window.viewportFixer = fixer;
580
- window.addEventListener('iframe-dimensions-applied', ((e) => {
581
- const ev = e;
582
- console.log('Iframe dimensions finalized:', ev.detail);
583
- }));
584
- return fixer;
585
805
  }
586
806
 
587
- class ViewportUnitsReplacer {
588
- regex() {
589
- return /([-.\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/gi;
590
- }
591
- config() {
592
- return window.__viewportConfig;
807
+ class IframeStyleReplacer {
808
+ doc;
809
+ win;
810
+ config;
811
+ regex = /([-.\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/gi;
812
+ constructor(iframe, config) {
813
+ if (!iframe.contentDocument || !iframe.contentWindow) {
814
+ throw new Error('Iframe document or window not accessible');
815
+ }
816
+ this.doc = iframe.contentDocument;
817
+ this.win = iframe.contentWindow;
818
+ this.config = config;
593
819
  }
594
820
  px(value) {
595
821
  return `${value.toFixed(2)}px`;
@@ -599,42 +825,44 @@ class ViewportUnitsReplacer {
599
825
  if (isNaN(num))
600
826
  return value;
601
827
  const map = {
602
- vh: this.config().targetHeight,
603
- svh: this.config().targetHeight,
604
- lvh: this.config().targetHeight,
605
- dvh: this.config().targetHeight,
606
- vw: this.config().targetWidth,
607
- svw: this.config().targetWidth,
608
- lvw: this.config().targetWidth,
609
- dvw: this.config().targetWidth,
828
+ vh: this.config.targetHeight,
829
+ svh: this.config.targetHeight,
830
+ lvh: this.config.targetHeight,
831
+ dvh: this.config.targetHeight,
832
+ vw: this.config.targetWidth,
833
+ svw: this.config.targetWidth,
834
+ lvw: this.config.targetWidth,
835
+ dvw: this.config.targetWidth,
610
836
  };
611
837
  return this.px((num / 100) * (map[unit.toLowerCase()] || 0));
612
838
  }
613
839
  replaceInText(cssText) {
614
- return cssText.replace(this.regex(), (_, value, unit) => this.convert(value, unit));
840
+ return cssText.replace(this.regex, (_, value, unit) => this.convert(value, unit));
615
841
  }
616
842
  processInlineStyles() {
617
843
  let count = 0;
618
- document.querySelectorAll('[style]').forEach((el) => {
844
+ this.doc.querySelectorAll('[style]').forEach((el) => {
619
845
  const style = el.getAttribute('style');
620
- if (style && this.regex().test(style)) {
846
+ if (style && this.regex.test(style)) {
847
+ this.regex.lastIndex = 0;
621
848
  el.setAttribute('style', this.replaceInText(style));
622
849
  count++;
623
850
  }
624
851
  });
625
- console.log(`[Iframe] Replaced ${count} inline style elements`);
852
+ console.log(`[IframeStyleReplacer] Replaced ${count} inline style elements`);
626
853
  return count;
627
854
  }
628
855
  processStyleTags() {
629
856
  let count = 0;
630
- document.querySelectorAll('style').forEach((tag) => {
857
+ this.doc.querySelectorAll('style').forEach((tag) => {
631
858
  const css = tag.textContent || '';
632
- if (this.regex().test(css)) {
859
+ if (this.regex.test(css)) {
860
+ this.regex.lastIndex = 0;
633
861
  tag.textContent = this.replaceInText(css);
634
862
  count++;
635
863
  }
636
864
  });
637
- console.log(`[Iframe] Replaced ${count} <style> tags`);
865
+ console.log(`[IframeStyleReplacer] Replaced ${count} <style> tags`);
638
866
  return count;
639
867
  }
640
868
  processRule(rule) {
@@ -644,7 +872,8 @@ class ViewportUnitsReplacer {
644
872
  for (let i = 0; i < style.length; i++) {
645
873
  const prop = style[i];
646
874
  const value = style.getPropertyValue(prop);
647
- if (value && this.regex().test(value)) {
875
+ if (value && this.regex.test(value)) {
876
+ this.regex.lastIndex = 0;
648
877
  style.setProperty(prop, this.replaceInText(value), style.getPropertyPriority(prop));
649
878
  count++;
650
879
  }
@@ -660,11 +889,11 @@ class ViewportUnitsReplacer {
660
889
  }
661
890
  processStylesheets() {
662
891
  let total = 0;
663
- Array.from(document.styleSheets).forEach((sheet) => {
892
+ Array.from(this.doc.styleSheets).forEach((sheet) => {
664
893
  try {
665
894
  // Bỏ qua external CSS (cross-origin)
666
- if (sheet.href && !sheet.href.startsWith(location.origin)) {
667
- console.log('[Iframe] Skipping external CSS:', sheet.href);
895
+ if (sheet.href && !sheet.href.startsWith(this.win.location.origin)) {
896
+ console.log('[IframeStyleReplacer] Skipping external CSS:', sheet.href);
668
897
  return;
669
898
  }
670
899
  const rules = sheet.cssRules || sheet.rules;
@@ -675,26 +904,27 @@ class ViewportUnitsReplacer {
675
904
  }
676
905
  }
677
906
  catch (e) {
678
- console.warn('[Iframe] Cannot read stylesheet (CORS?):', e.message);
907
+ console.warn('[IframeStyleReplacer] Cannot read stylesheet (CORS?):', e.message);
679
908
  }
680
909
  });
681
- console.log(`[Iframe] Replaced ${total} rules in stylesheets`);
910
+ console.log(`[IframeStyleReplacer] Replaced ${total} rules in stylesheets`);
682
911
  return total;
683
912
  }
684
913
  async processLinkedStylesheets() {
685
- const links = document.querySelectorAll('link[rel="stylesheet"]');
914
+ const links = this.doc.querySelectorAll('link[rel="stylesheet"]');
686
915
  let count = 0;
687
916
  for (const link of Array.from(links)) {
688
- if (!link.href.startsWith(location.origin)) {
689
- console.log('[Iframe] Skipping external CSS:', link.href);
917
+ if (!link.href.startsWith(this.win.location.origin)) {
918
+ console.log('[IframeStyleReplacer] Skipping external CSS:', link.href);
690
919
  continue;
691
920
  }
692
921
  try {
693
922
  const res = await fetch(link.href);
694
923
  let css = await res.text();
695
- if (this.regex().test(css)) {
924
+ if (this.regex.test(css)) {
925
+ this.regex.lastIndex = 0;
696
926
  css = this.replaceInText(css);
697
- const style = document.createElement('style');
927
+ const style = this.doc.createElement('style');
698
928
  style.textContent = css;
699
929
  style.dataset.originalHref = link.href;
700
930
  link.parentNode?.insertBefore(style, link);
@@ -703,30 +933,25 @@ class ViewportUnitsReplacer {
703
933
  }
704
934
  }
705
935
  catch (e) {
706
- console.warn('[Iframe] Cannot load CSS:', link.href, e);
936
+ console.warn('[IframeStyleReplacer] Cannot load CSS:', link.href, e);
707
937
  }
708
938
  }
709
- console.log(`[Iframe] Replaced ${count} linked CSS files`);
939
+ console.log(`[IframeStyleReplacer] Replaced ${count} linked CSS files`);
710
940
  return count;
711
941
  }
712
942
  getFinalHeight() {
713
943
  // Trigger reflow
714
- void document.body.offsetHeight;
715
- return Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight, document.documentElement.clientHeight);
944
+ void this.doc.body.offsetHeight;
945
+ return Math.max(this.doc.body.scrollHeight, this.doc.body.offsetHeight, this.doc.documentElement.scrollHeight, this.doc.documentElement.offsetHeight, this.doc.documentElement.clientHeight);
716
946
  }
717
- notifyParent(height) {
718
- window.parent.postMessage({
719
- type: 'IFRAME_HEIGHT_CALCULATED',
720
- height,
721
- width: document.body.scrollWidth,
722
- }, '*');
723
- console.log('[Iframe] Sent height to parent:', height);
947
+ getFinalWidth() {
948
+ return Math.max(this.doc.body.scrollWidth, this.doc.body.offsetWidth, this.doc.documentElement.scrollWidth, this.doc.documentElement.offsetWidth, this.doc.documentElement.clientWidth);
724
949
  }
725
950
  async waitForResources() {
726
- if ('fonts' in document) {
727
- await document.fonts.ready;
951
+ if ('fonts' in this.doc) {
952
+ await this.doc.fonts.ready;
728
953
  }
729
- const images = Array.from(document.images).filter((img) => !img.complete);
954
+ const images = Array.from(this.doc.images).filter((img) => !img.complete);
730
955
  if (images.length > 0) {
731
956
  await Promise.all(images.map((img) => new Promise((resolve) => {
732
957
  img.onload = img.onerror = resolve;
@@ -735,37 +960,131 @@ class ViewportUnitsReplacer {
735
960
  }
736
961
  async run() {
737
962
  try {
963
+ console.log('[IframeStyleReplacer] Starting viewport units replacement...');
738
964
  this.processInlineStyles();
739
965
  this.processStyleTags();
740
966
  this.processStylesheets();
741
967
  await this.processLinkedStylesheets();
742
968
  // await this.waitForResources();
743
- requestAnimationFrame(() => {
744
- const height = this.getFinalHeight();
745
- this.notifyParent(height);
969
+ return await new Promise((resolve) => {
970
+ requestAnimationFrame(() => {
971
+ const height = this.getFinalHeight();
972
+ const width = this.getFinalWidth();
973
+ console.log('[IframeStyleReplacer] Calculated dimensions:', { height, width });
974
+ resolve({ height, width });
975
+ });
746
976
  });
747
977
  }
748
978
  catch (err) {
749
- console.error('[Iframe] Critical error:', err);
750
- this.notifyParent(document.body.scrollHeight || 1000);
979
+ console.error('[IframeStyleReplacer] Critical error:', err);
980
+ return {
981
+ height: this.doc.body.scrollHeight || 1000,
982
+ width: this.doc.body.scrollWidth || 1000,
983
+ };
751
984
  }
752
985
  }
753
- init() {
754
- if (!window.__viewportConfig)
986
+ updateConfig(config) {
987
+ this.config = { ...this.config, ...config };
988
+ }
989
+ }
990
+
991
+ class IframeHelperFixer {
992
+ iframe;
993
+ config;
994
+ replacer = null;
995
+ navigationBlocker = null;
996
+ constructor(config) {
997
+ this.config = config;
998
+ this.iframe = config.iframe;
999
+ this.init();
1000
+ }
1001
+ async init() {
1002
+ if (!this.iframe) {
1003
+ console.error('[IframeHelper] iframe not found');
1004
+ this.config.onError?.(new Error('iframe not found'));
755
1005
  return;
756
- if (document.readyState === 'loading') {
757
- document.addEventListener('DOMContentLoaded', () => this.run());
1006
+ }
1007
+ // Wait for iframe to load completely
1008
+ if (this.iframe.contentDocument?.readyState === 'complete') {
1009
+ await this.process();
758
1010
  }
759
1011
  else {
760
- this.run();
1012
+ this.iframe.addEventListener('load', () => this.process());
1013
+ }
1014
+ }
1015
+ async process() {
1016
+ if (!this.iframe.contentDocument || !this.iframe.contentWindow) {
1017
+ console.error('[IframeHelper] Cannot access iframe document');
1018
+ this.config.onError?.(new Error('Cannot access iframe document'));
1019
+ return;
1020
+ }
1021
+ try {
1022
+ console.log('[IframeHelper] Processing viewport units...');
1023
+ // Create replacer instance
1024
+ this.replacer = new IframeStyleReplacer(this.iframe, this.config);
1025
+ // Create navigation blocker
1026
+ this.navigationBlocker = new IframeNavigationBlockerV2(this.iframe);
1027
+ // Run replacement
1028
+ const result = await this.replacer.run();
1029
+ console.log('[IframeHelper] Process completed:', result);
1030
+ // Trigger success callback
1031
+ this.config.onSuccess?.(result);
1032
+ // Dispatch custom event
1033
+ window.dispatchEvent(new CustomEvent('iframe-dimensions-applied', {
1034
+ detail: result,
1035
+ }));
1036
+ }
1037
+ catch (error) {
1038
+ console.error('[IframeHelper] Failed to process:', error);
1039
+ this.config.onError?.(error);
1040
+ }
1041
+ }
1042
+ async recalculate() {
1043
+ console.log('[IframeHelper] Recalculating...');
1044
+ await this.process();
1045
+ }
1046
+ updateConfig(config) {
1047
+ this.config = { ...this.config, ...config };
1048
+ if (this.replacer) {
1049
+ this.replacer.updateConfig(config);
761
1050
  }
762
1051
  }
1052
+ enableNavigationBlocking() {
1053
+ this.navigationBlocker?.enable();
1054
+ }
1055
+ enableNavigationBlockingMessage() {
1056
+ this.navigationBlocker?.enableMessage();
1057
+ }
1058
+ disableNavigationBlocking() {
1059
+ this.navigationBlocker?.disable();
1060
+ }
1061
+ disableNavigationBlockingMessage() {
1062
+ this.navigationBlocker?.disableMessage();
1063
+ }
1064
+ destroy() {
1065
+ this.replacer = null;
1066
+ this.navigationBlocker?.destroy();
1067
+ this.navigationBlocker = null;
1068
+ console.log('[IframeHelper] Destroyed');
1069
+ }
763
1070
  }
764
1071
 
765
- var viewportReplacer = /*#__PURE__*/Object.freeze({
766
- __proto__: null,
767
- default: ViewportUnitsReplacer
768
- });
1072
+ function initIframeHelperFixer(config) {
1073
+ const fixer = new IframeHelperFixer(config);
1074
+ window.addEventListener('iframe-dimensions-applied', ((e) => {
1075
+ const ev = e;
1076
+ console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
1077
+ }));
1078
+ window.addEventListener('iframe-navigation-blocked', ((e) => {
1079
+ const ev = e;
1080
+ console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
1081
+ }));
1082
+ window.addEventListener('iframe-form-submit', ((e) => {
1083
+ const ev = e;
1084
+ console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
1085
+ }));
1086
+ return fixer;
1087
+ }
769
1088
 
770
1089
  const scrollToElementIfNeeded = (visualRef, rect, scale) => {
771
1090
  if (!visualRef.current)
@@ -1292,7 +1611,7 @@ function useVizLiveRender() {
1292
1611
  if (!iframe || !htmlContent)
1293
1612
  return;
1294
1613
  setIsRenderViz(false);
1295
- reset$1(iframe, { width: contentWidth, height: wrapperHeight }, (height) => {
1614
+ reset(iframe, { width: contentWidth, height: wrapperHeight }, (height) => {
1296
1615
  height && setIframeHeight(height);
1297
1616
  setIsRenderViz(true);
1298
1617
  });
@@ -1301,8 +1620,8 @@ function useVizLiveRender() {
1301
1620
  iframeRef,
1302
1621
  };
1303
1622
  }
1304
- function reset$1(iframe, rect, onSuccess) {
1305
- const viewportFixer = initViewportFixer({
1623
+ function reset(iframe, rect, onSuccess) {
1624
+ const fixer = initIframeHelperFixer({
1306
1625
  targetWidth: rect.width,
1307
1626
  targetHeight: rect.height,
1308
1627
  iframe: iframe,
@@ -1311,8 +1630,8 @@ function reset$1(iframe, rect, onSuccess) {
1311
1630
  onSuccess(data.height);
1312
1631
  },
1313
1632
  });
1314
- viewportFixer.recalculate();
1315
- return iframe;
1633
+ // fixer.recalculate();
1634
+ fixer.enableNavigationBlocking();
1316
1635
  }
1317
1636
 
1318
1637
  let visualizer = new Visualizer();
@@ -1330,7 +1649,7 @@ const useHeatmapRender = () => {
1330
1649
  if (!iframe?.contentWindow)
1331
1650
  return;
1332
1651
  await visualizer.html(payloads, iframe.contentWindow);
1333
- reset(iframe, payloads, (height) => {
1652
+ initIframe(iframe, payloads, (height) => {
1334
1653
  height && setIframeHeight(height);
1335
1654
  setIsRenderViz(true);
1336
1655
  setVizRef(visualizer);
@@ -1348,11 +1667,11 @@ const useHeatmapRender = () => {
1348
1667
  iframeRef,
1349
1668
  };
1350
1669
  };
1351
- function reset(iframe, payloads, onSuccess) {
1670
+ function initIframe(iframe, payloads, onSuccess) {
1352
1671
  const { size } = findLastSizeOfDom(payloads);
1353
1672
  const docWidth = size.width ?? 0;
1354
1673
  const docHeight = size.height ?? 0;
1355
- const viewportFixer = initViewportFixer({
1674
+ initIframeHelperFixer({
1356
1675
  targetWidth: docWidth,
1357
1676
  targetHeight: docHeight,
1358
1677
  iframe: iframe,
@@ -1361,8 +1680,7 @@ function reset(iframe, payloads, onSuccess) {
1361
1680
  onSuccess(data.height);
1362
1681
  },
1363
1682
  });
1364
- viewportFixer.recalculate();
1365
- return iframe;
1683
+ // fixer.recalculate();
1366
1684
  }
1367
1685
 
1368
1686
  function isMobileDevice(userAgent) {
@@ -2367,7 +2685,7 @@ const VizLiveRenderer = () => {
2367
2685
  const VizLiveHeatmap = () => {
2368
2686
  const controls = useHeatmapControlStore((state) => state.controls);
2369
2687
  const isRendering = useHeatmapDataStore((state) => state.isRendering);
2370
- const iframeHeight = useHeatmapSingleStore((state) => state.iframeHeight);
2688
+ const iframeHeight = useHeatmapLiveStore((state) => state.iframeHeight);
2371
2689
  const wrapperHeight = useHeatmapLiveStore((state) => state.wrapperHeight);
2372
2690
  const setWrapperHeight = useHeatmapLiveStore((state) => state.setWrapperHeight);
2373
2691
  const reset = useHeatmapLiveStore((state) => state.reset);