@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
177 lines (173 loc) • 5.68 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import React, { Component } from 'react';
import Popper from 'popper.js'; // eslint-disable-line import/extensions
import rafSchedule from 'raf-schd';
import { positionPropToPopperPosition } from './internal/helpers';
const defaultState = {
hasExtractedStyles: false,
// We set these default offsets to prevent a flash of popper content in the wrong position
// which can cause incorrect height calculations. Popper will calculate these values
offsets: {
popper: {
left: -9999,
top: -9999,
position: null
}
},
// fix Safari parent width: https://product-fabric.atlassian.net/browse/ED-1784
cssPosition: 'absolute',
originalHeight: null,
maxHeight: null
};
export default class Layer extends Component {
constructor(props) {
super(props);
_defineProperty(this, "targetRef", /*#__PURE__*/React.createRef());
_defineProperty(this, "contentRef", /*#__PURE__*/React.createRef());
_defineProperty(this, "extractStyles", state => {
if (state) {
const popperHeight = state.offsets.popper.height;
const left = Math.round(state.offsets.popper.left);
const top = Math.round(state.offsets.popper.top);
const cssPosition = 'absolute';
const originalHeight = this.state.originalHeight || popperHeight;
const maxHeight = this.calculateMaxHeight(originalHeight, popperHeight, top, cssPosition);
this.setState({
// position: fixed or absolute
cssPosition,
hasExtractedStyles: true,
transform: `translate3d(${left}px, ${top}px, 0px)`,
originalHeight,
maxHeight
});
}
});
this.state = defaultState;
this.extractStyles = rafSchedule(this.extractStyles.bind(this));
}
componentDidMount() {
this.applyPopper(this.props);
}
UNSAFE_componentWillReceiveProps(nextProps) {
this.applyPopper(nextProps);
}
componentDidUpdate(prevProps, prevState) {
const {
onPositioned
} = this.props;
const {
hasExtractedStyles
} = this.state;
// This flag is set the first time the position is calculated from Popper and applied to the content
if (!prevState.hasExtractedStyles && hasExtractedStyles && onPositioned) {
onPositioned();
}
}
componentWillUnmount() {
// this.extractStyles.cancel();
if (this.popper) {
this.popper.destroy();
}
}
/* Calculate the max height of the popper if it's height is greater than the viewport to prevent
* the bottom of the popper not being viewable.
* Only works if the popper uses viewport as the boundary and has a fixed position ancestor.
*/
calculateMaxHeight(originalHeight, currentHeight, positionTop, cssPosition) {
let DocumentElementClientHeight = 0;
if (document.documentElement) {
DocumentElementClientHeight = document.documentElement.clientHeight;
}
if (cssPosition !== 'fixed') {
return null;
}
const viewportHeight = Math.max(DocumentElementClientHeight, window.innerHeight || 0);
return viewportHeight < originalHeight && currentHeight + positionTop >= viewportHeight - 50 ?
// allow some spacing either side of viewport height
viewportHeight - 12 : null;
}
applyPopper(props) {
if (!this.targetRef.current || !this.contentRef.current) {
return;
}
if (this.popper) {
this.popper.destroy();
}
// "new Popper(...)" operation is very expensive when called on virtual DOM.
// This condition reduces the number of calls so we can run our tests faster
// (time was reduced from 100s to 13s).
if (!props.content) {
return;
}
// we wrap our target in a div so that we can safely get a reference to it, but we pass the
// actual target to popper
const isAlwaysFixed = false;
const actualTarget = this.targetRef.current.children[0];
const popperOpts = {
placement: positionPropToPopperPosition(props.position),
onCreate: this.extractStyles,
onUpdate: this.extractStyles,
modifiers: {
applyStyle: {
enabled: false
},
hide: {
enabled: false
},
offset: {
enabled: true,
offset: this.props.offset
},
flip: {
enabled: false,
flipVariations: true,
boundariesElement: 'viewport',
padding: 0 // leave 0 pixels between popper and the boundariesElement
},
preventOverflow: {
enabled: false,
escapeWithReference: true
}
},
positionFixed: isAlwaysFixed
};
if (actualTarget) {
this.popper = new Popper(actualTarget, this.contentRef.current, popperOpts);
}
}
render() {
const {
transform,
hasExtractedStyles,
maxHeight
} = this.state;
const opacity = hasExtractedStyles ? {} : {
opacity: 0
};
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
ref: this.targetRef
}, this.props.children), /*#__PURE__*/React.createElement("div", {
ref: this.contentRef,
style: {
top: 0,
left: 0,
position: 'absolute',
transform,
maxHeight: maxHeight ? maxHeight : 'auto',
...opacity
}
}, this.props.content));
}
}
// working with extract-react-types
_defineProperty(Layer, "defaultProps", {
boundariesElement: 'viewport',
children: null,
content: null,
offset: '0, 0',
position: 'right middle',
zIndex: 400,
lockScroll: false,
isAlwaysFixed: false,
onPositioned: () => {}
});