UNPKG

@wordpress/block-editor

Version:
263 lines (215 loc) 9.02 kB
"use strict"; 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