Add coexistence checks to all enqueue methods to prevent loading both React and Grid.js assets simultaneously. Changes: - ReactAdmin.php: Only enqueue React assets when ?react=1 - Init.php: Skip Grid.js when React active on admin pages - Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0 - Customer.php, Product.php, License.php: Add coexistence checks Now the toggle between Classic and React versions works correctly. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
240 lines
7.7 KiB
JavaScript
240 lines
7.7 KiB
JavaScript
import { createElement } from "react";
|
|
/**
|
|
* WordPress dependencies
|
|
*/
|
|
import { renderToString, useRef, useState, useEffect } from '@wordpress/element';
|
|
import { useFocusableIframe, useMergeRefs } from '@wordpress/compose';
|
|
|
|
/**
|
|
* Internal dependencies
|
|
*/
|
|
|
|
const observeAndResizeJS = function () {
|
|
const {
|
|
MutationObserver
|
|
} = window;
|
|
if (!MutationObserver || !document.body || !window.parent) {
|
|
return;
|
|
}
|
|
function sendResize() {
|
|
const clientBoundingRect = document.body.getBoundingClientRect();
|
|
window.parent.postMessage({
|
|
action: 'resize',
|
|
width: clientBoundingRect.width,
|
|
height: clientBoundingRect.height
|
|
}, '*');
|
|
}
|
|
const observer = new MutationObserver(sendResize);
|
|
observer.observe(document.body, {
|
|
attributes: true,
|
|
attributeOldValue: false,
|
|
characterData: true,
|
|
characterDataOldValue: false,
|
|
childList: true,
|
|
subtree: true
|
|
});
|
|
window.addEventListener('load', sendResize, true);
|
|
|
|
// Hack: Remove viewport unit styles, as these are relative
|
|
// the iframe root and interfere with our mechanism for
|
|
// determining the unconstrained page bounds.
|
|
function removeViewportStyles(ruleOrNode) {
|
|
if (ruleOrNode.style) {
|
|
['width', 'height', 'minHeight', 'maxHeight'].forEach(function (style) {
|
|
if (/^\\d+(vw|vh|svw|lvw|dvw|svh|lvh|dvh|vi|svi|lvi|dvi|vb|svb|lvb|dvb|vmin|svmin|lvmin|dvmin|vmax|svmax|lvmax|dvmax)$/.test(ruleOrNode.style[style])) {
|
|
ruleOrNode.style[style] = '';
|
|
}
|
|
});
|
|
}
|
|
}
|
|
Array.prototype.forEach.call(document.querySelectorAll('[style]'), removeViewportStyles);
|
|
Array.prototype.forEach.call(document.styleSheets, function (stylesheet) {
|
|
Array.prototype.forEach.call(stylesheet.cssRules || stylesheet.rules, removeViewportStyles);
|
|
});
|
|
document.body.style.position = 'absolute';
|
|
document.body.style.width = '100%';
|
|
document.body.setAttribute('data-resizable-iframe-connected', '');
|
|
sendResize();
|
|
|
|
// Resize events can change the width of elements with 100% width, but we don't
|
|
// get an DOM mutations for that, so do the resize when the window is resized, too.
|
|
window.addEventListener('resize', sendResize, true);
|
|
};
|
|
|
|
// TODO: These styles shouldn't be coupled with WordPress.
|
|
const style = `
|
|
body {
|
|
margin: 0;
|
|
}
|
|
html,
|
|
body,
|
|
body > div {
|
|
width: 100%;
|
|
}
|
|
html.wp-has-aspect-ratio,
|
|
body.wp-has-aspect-ratio,
|
|
body.wp-has-aspect-ratio > div,
|
|
body.wp-has-aspect-ratio > div iframe {
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden; /* If it has an aspect ratio, it shouldn't scroll. */
|
|
}
|
|
body > div > * {
|
|
margin-top: 0 !important; /* Has to have !important to override inline styles. */
|
|
margin-bottom: 0 !important;
|
|
}
|
|
`;
|
|
|
|
/**
|
|
* This component provides an isolated environment for arbitrary HTML via iframes.
|
|
*
|
|
* ```jsx
|
|
* import { SandBox } from '@wordpress/components';
|
|
*
|
|
* const MySandBox = () => (
|
|
* <SandBox html="<p>Content</p>" title="SandBox" type="embed" />
|
|
* );
|
|
* ```
|
|
*/
|
|
function SandBox({
|
|
html = '',
|
|
title = '',
|
|
type,
|
|
styles = [],
|
|
scripts = [],
|
|
onFocus,
|
|
tabIndex
|
|
}) {
|
|
const ref = useRef();
|
|
const [width, setWidth] = useState(0);
|
|
const [height, setHeight] = useState(0);
|
|
function isFrameAccessible() {
|
|
try {
|
|
return !!ref.current?.contentDocument?.body;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
function trySandBox(forceRerender = false) {
|
|
if (!isFrameAccessible()) {
|
|
return;
|
|
}
|
|
const {
|
|
contentDocument,
|
|
ownerDocument
|
|
} = ref.current;
|
|
if (!forceRerender && null !== contentDocument?.body.getAttribute('data-resizable-iframe-connected')) {
|
|
return;
|
|
}
|
|
|
|
// Put the html snippet into a html document, and then write it to the iframe's document
|
|
// we can use this in the future to inject custom styles or scripts.
|
|
// Scripts go into the body rather than the head, to support embedded content such as Instagram
|
|
// that expect the scripts to be part of the body.
|
|
const htmlDoc = createElement("html", {
|
|
lang: ownerDocument.documentElement.lang,
|
|
className: type
|
|
}, createElement("head", null, createElement("title", null, title), createElement("style", {
|
|
dangerouslySetInnerHTML: {
|
|
__html: style
|
|
}
|
|
}), styles.map((rules, i) => createElement("style", {
|
|
key: i,
|
|
dangerouslySetInnerHTML: {
|
|
__html: rules
|
|
}
|
|
}))), createElement("body", {
|
|
"data-resizable-iframe-connected": "data-resizable-iframe-connected",
|
|
className: type
|
|
}, createElement("div", {
|
|
dangerouslySetInnerHTML: {
|
|
__html: html
|
|
}
|
|
}), createElement("script", {
|
|
type: "text/javascript",
|
|
dangerouslySetInnerHTML: {
|
|
__html: `(${observeAndResizeJS.toString()})();`
|
|
}
|
|
}), scripts.map(src => createElement("script", {
|
|
key: src,
|
|
src: src
|
|
}))));
|
|
|
|
// Writing the document like this makes it act in the same way as if it was
|
|
// loaded over the network, so DOM creation and mutation, script execution, etc.
|
|
// all work as expected.
|
|
contentDocument.open();
|
|
contentDocument.write('<!DOCTYPE html>' + renderToString(htmlDoc));
|
|
contentDocument.close();
|
|
}
|
|
useEffect(() => {
|
|
trySandBox();
|
|
function tryNoForceSandBox() {
|
|
trySandBox(false);
|
|
}
|
|
function checkMessageForResize(event) {
|
|
const iframe = ref.current;
|
|
|
|
// Verify that the mounted element is the source of the message.
|
|
if (!iframe || iframe.contentWindow !== event.source) {
|
|
return;
|
|
}
|
|
|
|
// Attempt to parse the message data as JSON if passed as string.
|
|
let data = event.data || {};
|
|
if ('string' === typeof data) {
|
|
try {
|
|
data = JSON.parse(data);
|
|
} catch (e) {}
|
|
}
|
|
|
|
// Update the state only if the message is formatted as we expect,
|
|
// i.e. as an object with a 'resize' action.
|
|
if ('resize' !== data.action) {
|
|
return;
|
|
}
|
|
setWidth(data.width);
|
|
setHeight(data.height);
|
|
}
|
|
const iframe = ref.current;
|
|
const defaultView = iframe?.ownerDocument?.defaultView;
|
|
|
|
// This used to be registered using <iframe onLoad={} />, but it made the iframe blank
|
|
// after reordering the containing block. See these two issues for more details:
|
|
// https://github.com/WordPress/gutenberg/issues/6146
|
|
// https://github.com/facebook/react/issues/18752
|
|
iframe?.addEventListener('load', tryNoForceSandBox, false);
|
|
defaultView?.addEventListener('message', checkMessageForResize);
|
|
return () => {
|
|
iframe?.removeEventListener('load', tryNoForceSandBox, false);
|
|
defaultView?.removeEventListener('message', checkMessageForResize);
|
|
};
|
|
// Ignore reason: passing `exhaustive-deps` will likely involve a more detailed refactor.
|
|
// See https://github.com/WordPress/gutenberg/pull/44378
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
useEffect(() => {
|
|
trySandBox();
|
|
// Ignore reason: passing `exhaustive-deps` will likely involve a more detailed refactor.
|
|
// See https://github.com/WordPress/gutenberg/pull/44378
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [title, styles, scripts]);
|
|
useEffect(() => {
|
|
trySandBox(true);
|
|
// Ignore reason: passing `exhaustive-deps` will likely involve a more detailed refactor.
|
|
// See https://github.com/WordPress/gutenberg/pull/44378
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [html, type]);
|
|
return createElement("iframe", {
|
|
ref: useMergeRefs([ref, useFocusableIframe()]),
|
|
title: title,
|
|
tabIndex: tabIndex,
|
|
className: "components-sandbox",
|
|
sandbox: "allow-scripts allow-same-origin allow-presentation",
|
|
onFocus: onFocus,
|
|
width: Math.ceil(width),
|
|
height: Math.ceil(height)
|
|
});
|
|
}
|
|
export default SandBox;
|
|
//# sourceMappingURL=index.js.map
|