UNPKG

@shopify/polaris

Version:

Shopify’s product component library

219 lines (198 loc) • 7.22 kB
import { objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js'; import React$1, { PureComponent } from 'react'; import { EventListener as EventListener$1 } from '../EventListener/EventListener.js'; import { layer } from '../shared.js'; import { getRectForNode, Rect } from '../../utilities/geometry.js'; import { classNames } from '../../utilities/css.js'; import { Scrollable as Scrollable$1 } from '../Scrollable/Scrollable.js'; import { windowRect, calculateVerticalPosition, calculateHorizontalPosition, rectIsOutsideOfRect, intersectionWithViewport } from './utilities/math.js'; import styles from './PositionedOverlay.scss.js'; var OBSERVER_CONFIG = { childList: true, subtree: true }; class PositionedOverlay extends PureComponent { constructor(props) { super(props); this.state = { measuring: true, activatorRect: getRectForNode(this.props.activator), right: undefined, left: undefined, top: 0, height: 0, width: null, positioning: 'below', zIndex: null, outsideScrollableContainer: false, lockPosition: false }; this.overlay = null; this.scrollableContainer = null; this.observer = void 0; this.overlayDetails = () => { var { measuring, left, right, positioning, height, activatorRect } = this.state; return { measuring, left, right, desiredHeight: height, positioning, activatorRect }; }; this.setOverlay = node => { this.overlay = node; }; this.handleMeasurement = () => { var { lockPosition, top } = this.state; this.observer.disconnect(); this.setState(({ left, top, right }) => ({ left, right, top, height: 0, positioning: 'below', measuring: true }), () => { if (this.overlay == null || this.scrollableContainer == null) { return; } var { activator, preferredPosition = 'below', preferredAlignment = 'center', onScrollOut, fullWidth, fixed, preferInputActivator = true } = this.props; var preferredActivator = preferInputActivator ? activator.querySelector('input') || activator : activator; var activatorRect = getRectForNode(preferredActivator); var currentOverlayRect = getRectForNode(this.overlay); var scrollableElement = isDocument(this.scrollableContainer) ? document.body : this.scrollableContainer; var scrollableContainerRect = getRectForNode(scrollableElement); var overlayRect = fullWidth ? new Rect(_objectSpread2(_objectSpread2({}, currentOverlayRect), {}, { width: activatorRect.width })) : currentOverlayRect; // If `body` is 100% height, it still acts as though it were not constrained to that size. This adjusts for that. if (scrollableElement === document.body) { scrollableContainerRect.height = document.body.scrollHeight; } var overlayMargins = this.overlay.firstElementChild && this.overlay.firstChild instanceof HTMLElement ? getMarginsForNode(this.overlay.firstElementChild) : { activator: 0, container: 0, horizontal: 0 }; var containerRect = windowRect(); var zIndexForLayer = getZIndexForLayerFromNode(activator); var zIndex = zIndexForLayer == null ? zIndexForLayer : zIndexForLayer + 1; var verticalPosition = calculateVerticalPosition(activatorRect, overlayRect, overlayMargins, scrollableContainerRect, containerRect, preferredPosition, fixed); var horizontalPosition = calculateHorizontalPosition(activatorRect, overlayRect, containerRect, overlayMargins, preferredAlignment); this.setState({ measuring: false, activatorRect: getRectForNode(activator), left: preferredAlignment !== 'right' ? horizontalPosition : undefined, right: preferredAlignment === 'right' ? horizontalPosition : undefined, top: lockPosition ? top : verticalPosition.top, lockPosition: Boolean(fixed), height: verticalPosition.height || 0, width: fullWidth ? overlayRect.width : null, positioning: verticalPosition.positioning, outsideScrollableContainer: onScrollOut != null && rectIsOutsideOfRect(activatorRect, intersectionWithViewport(scrollableContainerRect)), zIndex }, () => { if (!this.overlay) return; this.observer.observe(this.overlay, OBSERVER_CONFIG); }); }); }; this.observer = new MutationObserver(this.handleMeasurement); } componentDidMount() { this.scrollableContainer = Scrollable$1.forNode(this.props.activator); if (this.scrollableContainer && !this.props.fixed) { this.scrollableContainer.addEventListener('scroll', this.handleMeasurement); } this.handleMeasurement(); } componentWillUnmount() { if (this.scrollableContainer && !this.props.fixed) { this.scrollableContainer.removeEventListener('scroll', this.handleMeasurement); } } componentDidUpdate() { var { outsideScrollableContainer, top } = this.state; var { onScrollOut, active } = this.props; if (active && onScrollOut != null && top !== 0 && outsideScrollableContainer) { onScrollOut(); } } render() { var { left, right, top, zIndex, width } = this.state; var { render, fixed, preventInteraction, classNames: propClassNames } = this.props; var style = { top: top == null || isNaN(top) ? undefined : top, left: left == null || isNaN(left) ? undefined : left, right: right == null || isNaN(right) ? undefined : right, width: width == null || isNaN(width) ? undefined : width, zIndex: zIndex == null || isNaN(zIndex) ? undefined : zIndex }; var className = classNames(styles.PositionedOverlay, fixed && styles.fixed, preventInteraction && styles.preventInteraction, propClassNames); return /*#__PURE__*/React$1.createElement("div", { className: className, style: style, ref: this.setOverlay }, /*#__PURE__*/React$1.createElement(EventListener$1, { event: "resize", handler: this.handleMeasurement }), render(this.overlayDetails())); } } function getMarginsForNode(node) { var nodeStyles = window.getComputedStyle(node); return { activator: parseFloat(nodeStyles.marginTop || '0'), container: parseFloat(nodeStyles.marginBottom || '0'), horizontal: parseFloat(nodeStyles.marginLeft || '0') }; } function getZIndexForLayerFromNode(node) { var layerNode = node.closest(layer.selector) || document.body; var zIndex = layerNode === document.body ? 'auto' : parseInt(window.getComputedStyle(layerNode).zIndex || '0', 10); return zIndex === 'auto' || isNaN(zIndex) ? null : zIndex; } function isDocument(node) { return node === document; } export { PositionedOverlay };