@shopify/app-bridge-host
Version:
App Bridge Host contains components and middleware to be consumed by the app's host, as well as the host itself. The middleware and `Frame` component are responsible for facilitating communication between the client and host, and used to act on actions se
153 lines (150 loc) • 5.8 kB
JavaScript
import { __rest, __assign } from 'tslib';
import React, { useCallback, useMemo } from 'react';
import compose from '@shopify/react-compose';
import { Spinner, Modal as Modal$2 } from '@shopify/polaris-internal';
import { Context } from '@shopify/app-bridge-core';
import { Button, Modal as Modal$3 } from '@shopify/app-bridge-core/actions';
import { feature } from '../store/reducers/embeddedApp/modal/index.js';
import withFeature from '../withFeature.js';
import Frame from '../Frame.js';
import { useHostContext } from '../hooks/useHostContext.js';
import '@shopify/app-bridge-core/actions/Error';
import { buildAppUrl } from './utilities/appUrl.js';
var style = {
border: 'none',
width: '100%',
flex: '1',
display: 'flex',
};
var spinnerStyle = {
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
};
var DEFAULT_IFRAME_CONTENT_HEIGHT = 400;
var SMALL_IFRAME_CONTENT_HEIGHT = 150;
var allowedProtocols = ['https:', 'http:'];
/**
* Renders a Polaris Modal with the Context set to `Modal`
* When the `location` is defined, renders Frame component as the modal content
* @public
* @requires HostContext
* */
function Modal(props) {
var _a = useHostContext(), app = _a.app, config = _a.config;
var _b = props.actions, close = _b.close, clickFooterButton = _b.clickFooterButton, _c = props.store, _d = _c.content, content = _d === void 0 ? null : _d, height = _c.height, id = _c.id, location = _c.location, open = _c.open, primaryAction = _c.primaryAction, secondaryActions = _c.secondaryActions, size = _c.size, _e = _c.title, title = _e === void 0 ? '' : _e, loading = _c.loading, extraProps = __rest(props, ["actions", "store"]);
var modalRef = useCallback(function (node) {
if (node) {
var modalElement = document.querySelector("[class^='Polaris-Modal-Dialog__Modal']");
if (modalElement) {
modalElement.style.overflow = 'hidden';
}
}
}, []);
var modalStyles = __assign(__assign({}, style), { height: "".concat(height || getIframeHeight(size), "px"), maxHeight: loading ? '100%' : undefined });
var url = useMemo(function () {
if (!location) {
// needed to handle basic case of title & content
return;
}
var apiKey = config.apiKey, handle = config.handle, url = config.url;
return buildAppUrl({
handle: handle,
apiKey: apiKey,
url: url,
pathname: location,
search: takeSearch(location),
});
}, [location]);
try {
if (!allowedProtocols.includes(new URL(location || '').protocol)) {
return null;
}
}
catch (error) {
// Noop
}
// There is a bug in Polaris for the iframe height calculation
// It returns null when the modal is not opened and is never re-calculated
if (!open) {
return null;
}
var frameContent = url ? (React.createElement(Frame, __assign({}, extraProps, { config: config, style: modalStyles, context: Context.Modal, app: app, title: title, url: url.href, onUrlChange: updateIframeUrl }))) : (content);
var isSectioned = url === undefined;
var isTitleHidden = (title || '').trim() === '';
var polarisPrimaryAction = primaryAction ? createButton(primaryAction) : undefined;
var polarisSecondaryActions = modalSecondaryActions(secondaryActions);
var polarisModalSize = getPolarisModalSize(size);
function handleClose() {
close({ id: id });
}
var loadingSpinner = loading ? (React.createElement("div", { style: spinnerStyle },
React.createElement(Spinner, null))) : null;
return (React.createElement(Modal$2, { size: polarisModalSize, title: title, open: open, onClose: handleClose, primaryAction: polarisPrimaryAction, secondaryActions: polarisSecondaryActions, sectioned: isSectioned, titleHidden: isTitleHidden },
React.createElement("div", { ref: modalRef },
loadingSpinner,
frameContent)));
function createButton(button) {
var label = button.label, id = button.id, disabled = button.disabled, style = button.style, loading = button.loading;
var onAction = function () { return clickFooterButton(id); };
return {
content: label,
disabled: disabled,
destructive: style === Button.Style.Danger,
onAction: onAction,
loading: loading,
};
}
function modalSecondaryActions(actions) {
if (!Array.isArray(actions) || !actions.length) {
return;
}
return actions.map(createButton);
}
}
function getPolarisModalSize(size) {
switch (size) {
case Modal$3.Size.Large:
return 'large';
case Modal$3.Size.Small:
return 'small';
default:
return undefined;
}
}
function getIframeHeight(size) {
var isFullScreen = size === Modal$3.Size.Full;
if (isFullScreen) {
return;
}
if (size === Modal$3.Size.Small) {
return SMALL_IFRAME_CONTENT_HEIGHT;
}
return DEFAULT_IFRAME_CONTENT_HEIGHT;
}
/**
* Take the search parameters from a given URL
*/
function takeSearch(url) {
var match = url.match(/\?[^#]+/);
if (match === null) {
return undefined;
}
return match[0];
}
function updateIframeUrl(iframe, newUrl) {
if (!iframe || !iframe.contentWindow) {
return;
}
iframe.contentWindow.location.replace(newUrl);
}
/**
* The Modal feature with its reducer, actions and UI component
* @public
* */
var Modal$1 = compose(withFeature(feature))(Modal);
export { Modal, Modal$1 as default };