@wordpress/block-editor
Version:
263 lines (215 loc) • 9.02 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _element = require("@wordpress/element");
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _classnames = _interopRequireDefault(require("classnames"));
var _i18n = require("@wordpress/i18n");
var _compose = require("@wordpress/compose");
var _components = require("@wordpress/components");
var _data = require("@wordpress/data");
var _blockSelectionClearer = require("../block-selection-clearer");
var _writingFlow = require("../writing-flow");
var _useCompatibilityStyles = require("./use-compatibility-styles");
var _store = require("../../store");
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
/**
* Bubbles some event types (keydown, keypress, and dragover) to parent document
* document to ensure that the keyboard shortcuts and drag and drop work.
*
* Ideally, we should remove event bubbling in the future. Keyboard shortcuts
* should be context dependent, e.g. actions on blocks like Cmd+A should not
* work globally outside the block editor.
*
* @param {Document} doc Document to attach listeners to.
*/
function bubbleEvents(doc) {
const {
defaultView
} = doc;
const {
frameElement
} = defaultView;
function bubbleEvent(event) {
const prototype = Object.getPrototypeOf(event);
const constructorName = prototype.constructor.name;
const Constructor = window[constructorName];
const init = {};
for (const key in event) {
init[key] = event[key];
}
if (event instanceof defaultView.MouseEvent) {
const rect = frameElement.getBoundingClientRect();
init.clientX += rect.left;
init.clientY += rect.top;
}
const newEvent = new Constructor(event.type, init);
const cancelled = !frameElement.dispatchEvent(newEvent);
if (cancelled) {
event.preventDefault();
}
}
const eventTypes = ['dragover', 'mousemove'];
for (const name of eventTypes) {
doc.addEventListener(name, bubbleEvent);
}
}
function useParsedAssets(html) {
return (0, _element.useMemo)(() => {
const doc = document.implementation.createHTMLDocument('');
doc.body.innerHTML = html;
return Array.from(doc.body.children);
}, [html]);
}
async function loadScript(head, {
id,
src
}) {
return new Promise((resolve, reject) => {
const script = head.ownerDocument.createElement('script');
script.id = id;
if (src) {
script.src = src;
script.onload = () => resolve();
script.onerror = () => reject();
} else {
resolve();
}
head.appendChild(script);
});
}
function Iframe({
contentRef,
children,
tabIndex = 0,
scale = 1,
frameSize = 0,
expand = false,
readonly,
forwardedRef: ref,
...props
}) {
var _assets$styles;
const assets = (0, _data.useSelect)(select => select(_store.store).getSettings().__unstableResolvedAssets, []);
const [, forceRender] = (0, _element.useReducer)(() => ({}));
const [iframeDocument, setIframeDocument] = (0, _element.useState)();
const [bodyClasses, setBodyClasses] = (0, _element.useState)([]);
const compatStyles = (0, _useCompatibilityStyles.useCompatibilityStyles)();
const scripts = useParsedAssets(assets?.scripts);
const clearerRef = (0, _blockSelectionClearer.useBlockSelectionClearer)();
const [before, writingFlowRef, after] = (0, _writingFlow.useWritingFlow)();
const [contentResizeListener, {
height: contentHeight
}] = (0, _compose.useResizeObserver)();
const setRef = (0, _compose.useRefEffect)(node => {
let iFrameDocument; // Prevent the default browser action for files dropped outside of dropzones.
function preventFileDropDefault(event) {
event.preventDefault();
}
function onLoad() {
const {
contentDocument,
ownerDocument
} = node;
const {
documentElement
} = contentDocument;
iFrameDocument = contentDocument;
bubbleEvents(contentDocument);
setIframeDocument(contentDocument);
clearerRef(documentElement); // Ideally ALL classes that are added through get_body_class should
// be added in the editor too, which we'll somehow have to get from
// the server in the future (which will run the PHP filters).
setBodyClasses(Array.from(ownerDocument.body.classList).filter(name => name.startsWith('admin-color-') || name.startsWith('post-type-') || name === 'wp-embed-responsive'));
contentDocument.dir = ownerDocument.dir;
documentElement.removeChild(contentDocument.body);
for (const compatStyle of compatStyles) {
if (contentDocument.getElementById(compatStyle.id)) {
continue;
}
contentDocument.head.appendChild(compatStyle.cloneNode(true)); // eslint-disable-next-line no-console
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);
}
node.addEventListener('load', onLoad);
return () => {
node.removeEventListener('load', onLoad);
iFrameDocument?.removeEventListener('dragover', preventFileDropDefault);
iFrameDocument?.removeEventListener('drop', preventFileDropDefault);
};
}, []);
const headRef = (0, _compose.useRefEffect)(element => {
scripts.reduce((promise, script) => promise.then(() => loadScript(element, script)), Promise.resolve()).finally(() => {
// When script are loaded, re-render blocks to allow them
// to initialise.
forceRender();
});
}, []);
const disabledRef = (0, _compose.useDisabled)({
isDisabled: !readonly
});
const bodyRef = (0, _compose.useMergeRefs)([contentRef, clearerRef, writingFlowRef, disabledRef, headRef]); // Correct doctype is required to enable rendering in standards
// mode. Also preload the styles to avoid a flash of unstyled
// content.
const html = '<!doctype html>' + '<style>html{height:auto!important;}body{margin:0}</style>' + ((_assets$styles = assets?.styles) !== null && _assets$styles !== void 0 ? _assets$styles : '');
const [src, cleanup] = (0, _element.useMemo)(() => {
const _src = URL.createObjectURL(new window.Blob([html], {
type: 'text/html'
}));
return [_src, () => URL.revokeObjectURL(_src)];
}, [html]);
(0, _element.useEffect)(() => cleanup, [cleanup]); // We need to counter the margin created by scaling the iframe. If the scale
// is e.g. 0.45, then the top + bottom margin is 0.55 (1 - scale). Just the
// top or bottom margin is 0.55 / 2 ((1 - scale) / 2).
const marginFromScaling = contentHeight * (1 - scale) / 2;
return (0, _element.createElement)(_element.Fragment, null, tabIndex >= 0 && before, (0, _element.createElement)("iframe", (0, _extends2.default)({}, props, {
style: { ...props.style,
height: expand ? contentHeight : props.style?.height,
marginTop: scale !== 1 ? -marginFromScaling + frameSize : props.style?.marginTop,
marginBottom: scale !== 1 ? -marginFromScaling + frameSize : props.style?.marginBottom,
transform: scale !== 1 ? `scale( ${scale} )` : props.style?.transform,
transition: 'all .3s'
},
ref: (0, _compose.useMergeRefs)([ref, setRef]),
tabIndex: tabIndex // Correct doctype is required to enable rendering in standards
// mode. Also preload the styles to avoid a flash of unstyled
// content.
,
src: src,
title: (0, _i18n.__)('Editor canvas')
}), iframeDocument && (0, _element.createPortal)((0, _element.createElement)("body", {
ref: bodyRef,
className: (0, _classnames.default)('block-editor-iframe__body', 'editor-styles-wrapper', ...bodyClasses)
}, contentResizeListener, (0, _element.createElement)(_components.__experimentalStyleProvider, {
document: iframeDocument
}, children)), iframeDocument.documentElement)), tabIndex >= 0 && after);
}
function IframeIfReady(props, ref) {
const isInitialised = (0, _data.useSelect)(select => select(_store.store).getSettings().__internalIsInitialized, []); // We shouldn't render the iframe until the editor settings are initialised.
// The initial settings are needed to get the styles for the srcDoc, which
// cannot be changed after the iframe is mounted. srcDoc is used to to set
// the initial iframe HTML, which is required to avoid a flash of unstyled
// content.
if (!isInitialised) {
return null;
}
return (0, _element.createElement)(Iframe, (0, _extends2.default)({}, props, {
forwardedRef: ref
}));
}
var _default = (0, _element.forwardRef)(IframeIfReady);
exports.default = _default;
//# sourceMappingURL=index.js.map