@shopify/polaris
Version:
Shopify’s product component library
219 lines (198 loc) • 7.22 kB
JavaScript
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 };