@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.
- package/dist/esm/components/VizLive/VizLiveHeatmap.d.ts.map +1 -1
- package/dist/esm/helpers/iframe-helper/fixer.d.ts +18 -0
- package/dist/esm/helpers/iframe-helper/fixer.d.ts.map +1 -0
- package/dist/esm/helpers/iframe-helper/index.d.ts +2 -0
- package/dist/esm/helpers/iframe-helper/index.d.ts.map +1 -0
- package/dist/esm/helpers/iframe-helper/init.d.ts +5 -0
- package/dist/esm/helpers/iframe-helper/init.d.ts.map +1 -0
- package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts +28 -0
- package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -0
- package/dist/esm/helpers/iframe-helper/navigation-blocker.d.ts +20 -0
- package/dist/esm/helpers/iframe-helper/navigation-blocker.d.ts.map +1 -0
- package/dist/esm/helpers/iframe-helper/style-replacer.d.ts +25 -0
- package/dist/esm/helpers/iframe-helper/style-replacer.d.ts.map +1 -0
- package/dist/esm/helpers/index.d.ts +1 -2
- package/dist/esm/helpers/index.d.ts.map +1 -1
- package/dist/esm/index.js +447 -129
- package/dist/esm/index.mjs +447 -129
- package/dist/esm/types/iframe-helper.d.ts +20 -0
- package/dist/esm/types/iframe-helper.d.ts.map +1 -0
- package/dist/esm/types/index.d.ts +1 -1
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/umd/components/VizLive/VizLiveHeatmap.d.ts.map +1 -1
- package/dist/umd/helpers/iframe-helper/fixer.d.ts +18 -0
- package/dist/umd/helpers/iframe-helper/fixer.d.ts.map +1 -0
- package/dist/umd/helpers/iframe-helper/index.d.ts +2 -0
- package/dist/umd/helpers/iframe-helper/index.d.ts.map +1 -0
- package/dist/umd/helpers/iframe-helper/init.d.ts +5 -0
- package/dist/umd/helpers/iframe-helper/init.d.ts.map +1 -0
- package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts +28 -0
- package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -0
- package/dist/umd/helpers/iframe-helper/navigation-blocker.d.ts +20 -0
- package/dist/umd/helpers/iframe-helper/navigation-blocker.d.ts.map +1 -0
- package/dist/umd/helpers/iframe-helper/style-replacer.d.ts +25 -0
- package/dist/umd/helpers/iframe-helper/style-replacer.d.ts.map +1 -0
- package/dist/umd/helpers/index.d.ts +1 -2
- package/dist/umd/helpers/index.d.ts.map +1 -1
- package/dist/umd/index.js +2 -2
- package/dist/umd/types/iframe-helper.d.ts +20 -0
- package/dist/umd/types/iframe-helper.d.ts.map +1 -0
- package/dist/umd/types/index.d.ts +1 -1
- package/dist/umd/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/esm/helpers/viewport-fixer.d.ts +0 -13
- package/dist/esm/helpers/viewport-fixer.d.ts.map +0 -1
- package/dist/esm/helpers/viewport-replacer.d.ts +0 -25
- package/dist/esm/helpers/viewport-replacer.d.ts.map +0 -1
- package/dist/esm/types/viewport-fixer.d.ts +0 -30
- package/dist/esm/types/viewport-fixer.d.ts.map +0 -1
- package/dist/umd/helpers/viewport-fixer.d.ts +0 -13
- package/dist/umd/helpers/viewport-fixer.d.ts.map +0 -1
- package/dist/umd/helpers/viewport-replacer.d.ts +0 -25
- package/dist/umd/helpers/viewport-replacer.d.ts.map +0 -1
- package/dist/umd/types/viewport-fixer.d.ts +0 -30
- 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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
-
|
|
548
|
-
|
|
546
|
+
catch (error) {
|
|
547
|
+
console.error('[NavigationBlocker] Init error:', error);
|
|
549
548
|
}
|
|
550
549
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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.
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
741
|
+
shouldShowMessage(url) {
|
|
742
|
+
return !url.startsWith('#') && url !== 'reload' && url !== 'popup';
|
|
743
|
+
}
|
|
744
|
+
showBlockedMessage(url) {
|
|
745
|
+
if (!this.showMessage)
|
|
570
746
|
return;
|
|
571
|
-
this.
|
|
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
|
-
|
|
574
|
-
this.
|
|
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
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
|
603
|
-
svh: this.config
|
|
604
|
-
lvh: this.config
|
|
605
|
-
dvh: this.config
|
|
606
|
-
vw: this.config
|
|
607
|
-
svw: this.config
|
|
608
|
-
lvw: this.config
|
|
609
|
-
dvw: this.config
|
|
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
|
|
840
|
+
return cssText.replace(this.regex, (_, value, unit) => this.convert(value, unit));
|
|
615
841
|
}
|
|
616
842
|
processInlineStyles() {
|
|
617
843
|
let count = 0;
|
|
618
|
-
|
|
844
|
+
this.doc.querySelectorAll('[style]').forEach((el) => {
|
|
619
845
|
const style = el.getAttribute('style');
|
|
620
|
-
if (style && this.regex
|
|
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(`[
|
|
852
|
+
console.log(`[IframeStyleReplacer] Replaced ${count} inline style elements`);
|
|
626
853
|
return count;
|
|
627
854
|
}
|
|
628
855
|
processStyleTags() {
|
|
629
856
|
let count = 0;
|
|
630
|
-
|
|
857
|
+
this.doc.querySelectorAll('style').forEach((tag) => {
|
|
631
858
|
const css = tag.textContent || '';
|
|
632
|
-
if (this.regex
|
|
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(`[
|
|
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
|
|
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(
|
|
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('[
|
|
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('[
|
|
907
|
+
console.warn('[IframeStyleReplacer] Cannot read stylesheet (CORS?):', e.message);
|
|
679
908
|
}
|
|
680
909
|
});
|
|
681
|
-
console.log(`[
|
|
910
|
+
console.log(`[IframeStyleReplacer] Replaced ${total} rules in stylesheets`);
|
|
682
911
|
return total;
|
|
683
912
|
}
|
|
684
913
|
async processLinkedStylesheets() {
|
|
685
|
-
const links =
|
|
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('[
|
|
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
|
|
924
|
+
if (this.regex.test(css)) {
|
|
925
|
+
this.regex.lastIndex = 0;
|
|
696
926
|
css = this.replaceInText(css);
|
|
697
|
-
const 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('[
|
|
936
|
+
console.warn('[IframeStyleReplacer] Cannot load CSS:', link.href, e);
|
|
707
937
|
}
|
|
708
938
|
}
|
|
709
|
-
console.log(`[
|
|
939
|
+
console.log(`[IframeStyleReplacer] Replaced ${count} linked CSS files`);
|
|
710
940
|
return count;
|
|
711
941
|
}
|
|
712
942
|
getFinalHeight() {
|
|
713
943
|
// Trigger reflow
|
|
714
|
-
void
|
|
715
|
-
return Math.max(
|
|
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
|
-
|
|
718
|
-
|
|
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
|
|
727
|
-
await
|
|
951
|
+
if ('fonts' in this.doc) {
|
|
952
|
+
await this.doc.fonts.ready;
|
|
728
953
|
}
|
|
729
|
-
const images = Array.from(
|
|
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
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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('[
|
|
750
|
-
|
|
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
|
-
|
|
754
|
-
|
|
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
|
-
|
|
757
|
-
|
|
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.
|
|
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
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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
|
|
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
|
|
1305
|
-
const
|
|
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
|
-
|
|
1315
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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);
|