UNPKG

@gechiui/components

Version:
318 lines (276 loc) 9.62 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _element = require("@gechiui/element"); var _reactNative = require("react-native"); var _reactNativeWebview = require("react-native-webview"); var _compose = require("@gechiui/compose"); var _style = _interopRequireDefault(require("./style.scss")); /** * External dependencies */ /** * GeChiUI dependencies */ /** * Internal dependencies */ const observeAndResizeJS = ` ( function() { var observer; if ( ! window.MutationObserver || ! document.body || ! window.parent ) { return; } function sendResize() { var clientBoundingRect = document.body.getBoundingClientRect(); // The function postMessage is exposed by the react-native-webview library // to communicate between React Native and the WebView, in this case, // we use it for notifying resize changes. window.ReactNativeWebView.postMessage(JSON.stringify( { action: 'resize', width: clientBoundingRect.width, height: clientBoundingRect.height, })); } 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+(vmin|vmax|vh|vw)$/.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 ); } )();`; const style = ` body { margin: 0; } html, body, body > div, body > div iframe { width: 100%; } body > div > * { margin-top: 0 !important; /* Has to have !important to override inline styles. */ margin-bottom: 0 !important; } .gc-block-embed__wrapper { position: relative; } body.gc-has-aspect-ratio > div iframe { height: 100%; overflow: hidden; /* If it has an aspect ratio, it shouldn't scroll. */ } /** * Add responsiveness to embeds with aspect ratios. * * These styles have been copied from the web version (https://git.io/JEFcX) and * adapted for the native version. */ .gc-has-aspect-ratio.gc-block-embed__wrapper::before { content: ""; display: block; padding-top: 50%; // Default to 2:1 aspect ratio. } .gc-has-aspect-ratio iframe { position: absolute; top: 0; right: 0; bottom: 0; left: 0; height: 100%; width: 100%; } .gc-embed-aspect-21-9.gc-block-embed__wrapper::before { padding-top: 42.85%; // 9 / 21 * 100 } .gc-embed-aspect-18-9.gc-block-embed__wrapper::before { padding-top: 50%; // 9 / 18 * 100 } .gc-embed-aspect-16-9.gc-block-embed__wrapper::before { padding-top: 56.25%; // 9 / 16 * 100 } .gc-embed-aspect-4-3.gc-block-embed__wrapper::before { padding-top: 75%; // 3 / 4 * 100 } .gc-embed-aspect-1-1.gc-block-embed__wrapper::before { padding-top: 100%; // 1 / 1 * 100 } .gc-embed-aspect-9-16.gc-block-embed__wrapper::before { padding-top: 177.77%; // 16 / 9 * 100 } .gc-embed-aspect-1-2.gc-block-embed__wrapper::before { padding-top: 200%; // 2 / 1 * 100 } `; const EMPTY_ARRAY = []; function Sandbox(_ref) { let { containerStyle, customJS, html = '', lang = 'en', providerUrl = '', scripts = EMPTY_ARRAY, styles = EMPTY_ARRAY, title = '', type, url } = _ref; const colorScheme = (0, _compose.usePreferredColorScheme)(); const ref = (0, _element.useRef)(); const [height, setHeight] = (0, _element.useState)(0); const [contentHtml, setContentHtml] = (0, _element.useState)(getHtmlDoc()); const windowSize = _reactNative.Dimensions.get('window'); const [isLandscape, setIsLandscape] = (0, _element.useState)(windowSize.width >= windowSize.height); const wasLandscape = (0, _element.useRef)(isLandscape); // On Android, we need to recreate the WebView on any of the following actions, otherwise it disappears: // - Device rotation // - Light/dark mode changes // For this purpose, the key prop used in the WebView will be updated with the value of the actions. const key = _reactNative.Platform.select({ android: `${url}-${isLandscape ? 'landscape' : 'portrait'}-${colorScheme}`, ios: url }); function getHtmlDoc() { // Put the html snippet into a html document, and update the state to refresh the WebView, // 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 = (0, _element.createElement)("html", { lang: lang }, (0, _element.createElement)("head", null, (0, _element.createElement)("title", null, title), (0, _element.createElement)("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), (0, _element.createElement)("style", { dangerouslySetInnerHTML: { __html: style } }), styles.map((rules, i) => (0, _element.createElement)("style", { key: i, dangerouslySetInnerHTML: { __html: rules } }))), (0, _element.createElement)("body", { "data-resizable-iframe-connected": "data-resizable-iframe-connected", className: type }, (0, _element.createElement)("div", { dangerouslySetInnerHTML: { __html: html } }), (0, _element.createElement)("script", { type: "text/javascript", dangerouslySetInnerHTML: { __html: customJS || observeAndResizeJS } }), scripts.map(src => (0, _element.createElement)("script", { key: src, src: src })))); return '<!DOCTYPE html>' + (0, _element.renderToString)(htmlDoc); } function updateContentHtml() { let forceRerender = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; const newContentHtml = getHtmlDoc(); if (forceRerender && contentHtml === newContentHtml) { // The re-render is forced by updating the state with empty HTML, // waiting for the JS code to be executed with "setImmediate" and then // setting the content HTML again. setContentHtml(''); setImmediate(() => setContentHtml(newContentHtml)); } else { setContentHtml(newContentHtml); } } function checkMessageForResize(event) { // Attempt to parse the message data as JSON if passed as string let data = event.nativeEvent.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; } setHeight(data.height); } function getSizeStyle() { const contentHeight = Math.ceil(height); return contentHeight ? { height: contentHeight } : { aspectRatio: 1 }; } function onChangeDimensions(dimensions) { setIsLandscape(dimensions.window.width >= dimensions.window.height); } (0, _element.useEffect)(() => { _reactNative.Dimensions.addEventListener('change', onChangeDimensions); return () => { _reactNative.Dimensions.removeEventListener('change', onChangeDimensions); }; }, []); (0, _element.useEffect)(() => { updateContentHtml(); }, [html, title, type, styles, scripts]); (0, _element.useEffect)(() => { // When device orientation changes we have to recalculate the size, // for this purpose we reset the current size value. if (wasLandscape.current !== isLandscape) { setHeight(0); } wasLandscape.current = isLandscape; }, [isLandscape]); return (0, _element.createElement)(_reactNativeWebview.WebView, { containerStyle: [_style.default['sandbox-webview__container'], containerStyle], key: key, ref: ref, source: { baseUrl: providerUrl, html: contentHtml } // Wildcard value is required for static HTML // Reference: https://github.com/react-native-webview/react-native-webview/blob/master/docs/Reference.md#source , originWhitelist: ['*'], style: [_style.default['sandbox-webview__content'], getSizeStyle()], onMessage: checkMessageForResize, scrollEnabled: false, setBuiltInZoomControls: false, showsHorizontalScrollIndicator: false, showsVerticalScrollIndicator: false }); } var _default = (0, _element.memo)(Sandbox); exports.default = _default; //# sourceMappingURL=index.native.js.map