@wordpress/block-editor
Version:
295 lines (294 loc) • 8.96 kB
JavaScript
// packages/block-editor/src/components/iframe/index.js
import clsx from "clsx";
import {
useState,
createPortal,
forwardRef,
useMemo,
useEffect
} from "@wordpress/element";
import { __ } from "@wordpress/i18n";
import { useMergeRefs, useRefEffect, useDisabled } from "@wordpress/compose";
import { __experimentalStyleProvider as StyleProvider } from "@wordpress/components";
import { useSelect } from "@wordpress/data";
import { useBlockSelectionClearer } from "../block-selection-clearer";
import { useWritingFlow } from "../writing-flow";
import { getCompatibilityStyles } from "./get-compatibility-styles";
import { useScaleCanvas } from "./use-scale-canvas";
import { store as blockEditorStore } from "../../store";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function bubbleEvent(event, Constructor, frame) {
const init = {};
for (const key in event) {
init[key] = event[key];
}
if (event instanceof frame.contentDocument.defaultView.MouseEvent) {
const rect = frame.getBoundingClientRect();
init.clientX += rect.left;
init.clientY += rect.top;
}
const newEvent = new Constructor(event.type, init);
if (init.defaultPrevented) {
newEvent.preventDefault();
}
const cancelled = !frame.dispatchEvent(newEvent);
if (cancelled) {
event.preventDefault();
}
}
function useBubbleEvents(iframeDocument) {
return useRefEffect(() => {
const { defaultView } = iframeDocument;
if (!defaultView) {
return;
}
const { frameElement } = defaultView;
const html = iframeDocument.documentElement;
const eventTypes = ["dragover", "mousemove"];
const handlers = {};
for (const name of eventTypes) {
handlers[name] = (event) => {
const prototype = Object.getPrototypeOf(event);
const constructorName = prototype.constructor.name;
const Constructor = window[constructorName];
bubbleEvent(event, Constructor, frameElement);
};
html.addEventListener(name, handlers[name]);
}
return () => {
for (const name of eventTypes) {
html.removeEventListener(name, handlers[name]);
}
};
});
}
function Iframe({
contentRef,
children,
tabIndex = 0,
scale = 1,
frameSize = 0,
readonly,
forwardedRef: ref,
title = __("Editor canvas"),
...props
}) {
const { resolvedAssets, isPreviewMode } = useSelect((select) => {
const { getSettings } = select(blockEditorStore);
const settings = getSettings();
return {
resolvedAssets: settings.__unstableResolvedAssets,
isPreviewMode: settings.isPreviewMode
};
}, []);
const { styles = "", scripts = "" } = resolvedAssets;
const [iframeDocument, setIframeDocument] = useState();
const [bodyClasses, setBodyClasses] = useState([]);
const clearerRef = useBlockSelectionClearer();
const [before, writingFlowRef, after] = useWritingFlow();
const setRef = useRefEffect((node) => {
node._load = () => {
setIframeDocument(node.contentDocument);
};
let iFrameDocument;
function preventFileDropDefault(event) {
event.preventDefault();
}
function interceptLinkClicks(event) {
if (event.target.tagName === "A" && event.target.getAttribute("href")?.startsWith("#")) {
event.preventDefault();
iFrameDocument.defaultView.location.hash = event.target.getAttribute("href").slice(1);
}
}
const { ownerDocument } = node;
setBodyClasses(
Array.from(ownerDocument.body.classList).filter(
(name) => name.startsWith("admin-color-") || name.startsWith("post-type-") || name === "wp-embed-responsive"
)
);
function onLoad() {
const { contentDocument } = node;
const { documentElement } = contentDocument;
iFrameDocument = contentDocument;
documentElement.classList.add("block-editor-iframe__html");
clearerRef(documentElement);
contentDocument.dir = ownerDocument.dir;
for (const compatStyle of getCompatibilityStyles()) {
if (contentDocument.getElementById(compatStyle.id)) {
continue;
}
contentDocument.head.appendChild(
compatStyle.cloneNode(true)
);
if (!isPreviewMode) {
console.warn(
`${compatStyle.id} was added to the iframe incorrectly. Please use block.json or enqueue_block_assets to add styles to the iframe.`,
compatStyle
);
}
}
iFrameDocument.addEventListener(
"dragover",
preventFileDropDefault,
false
);
iFrameDocument.addEventListener(
"drop",
preventFileDropDefault,
false
);
iFrameDocument.addEventListener("click", interceptLinkClicks);
}
node.addEventListener("load", onLoad);
return () => {
delete node._load;
node.removeEventListener("load", onLoad);
iFrameDocument?.removeEventListener(
"dragover",
preventFileDropDefault
);
iFrameDocument?.removeEventListener(
"drop",
preventFileDropDefault
);
iFrameDocument?.removeEventListener("click", interceptLinkClicks);
};
}, []);
const {
contentResizeListener,
containerResizeListener,
isZoomedOut,
scaleContainerWidth
} = useScaleCanvas({
scale,
frameSize: parseInt(frameSize),
iframeDocument
});
const disabledRef = useDisabled({ isDisabled: !readonly });
const bodyRef = useMergeRefs([
useBubbleEvents(iframeDocument),
contentRef,
clearerRef,
writingFlowRef,
disabledRef
]);
const html = `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<base href="${window.location.origin}">
<script>window.frameElement._load()</script>
<style>
html{
height: auto !important;
min-height: 100%;
}
/* Lowest specificity to not override global styles */
:where(body) {
margin: 0;
/* Default background color in case zoom out mode background
colors the html element */
background-color: white;
}
</style>
${styles}
${scripts}
</head>
<body>
<script>document.currentScript.parentElement.remove()</script>
</body>
</html>`;
const [src, cleanup] = useMemo(() => {
const _src = URL.createObjectURL(
new window.Blob([html], { type: "text/html" })
);
return [_src, () => URL.revokeObjectURL(_src)];
}, [html]);
useEffect(() => cleanup, [cleanup]);
const shouldRenderFocusCaptureElements = tabIndex >= 0 && !isPreviewMode;
const iframe = /* @__PURE__ */ jsxs(Fragment, { children: [
shouldRenderFocusCaptureElements && before,
/* @__PURE__ */ jsx(
"iframe",
{
...props,
style: {
...props.style,
height: props.style?.height,
border: 0
},
ref: useMergeRefs([ref, setRef]),
tabIndex,
src,
title,
onKeyDown: (event) => {
if (props.onKeyDown) {
props.onKeyDown(event);
}
if (event.currentTarget.ownerDocument !== event.target.ownerDocument) {
const { stopPropagation } = event.nativeEvent;
event.nativeEvent.stopPropagation = () => {
};
event.stopPropagation();
event.nativeEvent.stopPropagation = stopPropagation;
bubbleEvent(
event,
window.KeyboardEvent,
event.currentTarget
);
}
},
children: iframeDocument && createPortal(
/* @__PURE__ */ jsxs(
"body",
{
ref: bodyRef,
className: clsx(
"block-editor-iframe__body",
"editor-styles-wrapper",
...bodyClasses
),
children: [
contentResizeListener,
/* @__PURE__ */ jsx(StyleProvider, { document: iframeDocument, children })
]
}
),
iframeDocument.documentElement
)
}
),
shouldRenderFocusCaptureElements && after
] });
return /* @__PURE__ */ jsxs("div", { className: "block-editor-iframe__container", children: [
containerResizeListener,
/* @__PURE__ */ jsx(
"div",
{
className: clsx(
"block-editor-iframe__scale-container",
isZoomedOut && "is-zoomed-out"
),
style: {
"--wp-block-editor-iframe-zoom-out-scale-container-width": isZoomedOut && `${scaleContainerWidth}px`
},
children: iframe
}
)
] });
}
function IframeIfReady(props, ref) {
const isInitialised = useSelect(
(select) => select(blockEditorStore).getSettings().__internalIsInitialized,
[]
);
if (!isInitialised) {
return null;
}
return /* @__PURE__ */ jsx(Iframe, { ...props, forwardedRef: ref });
}
var iframe_default = forwardRef(IframeIfReady);
export {
iframe_default as default
};
//# sourceMappingURL=index.js.map