grommet
Version:
focus on the essential experience
350 lines (272 loc) • 12.7 kB
JavaScript
;
exports.__esModule = true;
exports.DropContainer = void 0;
var _react = _interopRequireWildcard(require("react"));
var _defaultProps = require("../../default-props");
var _contexts = require("../../contexts");
var _FocusedContainer = require("../FocusedContainer");
var _utils = require("../../utils");
var _Box = require("../Box");
var _Keyboard = require("../Keyboard");
var _StyledDrop = require("./StyledDrop");
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
// using react synthetic event to be able to stop propagation that
// would otherwise close the layer on ESC.
var preventLayerClose = function preventLayerClose(event) {
var key = event.keyCode ? event.keyCode : event.which;
if (key === 27) {
event.stopPropagation();
}
};
var DropContainer =
/*#__PURE__*/
function (_Component) {
_inheritsLoose(DropContainer, _Component);
function DropContainer() {
var _this;
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = _Component.call.apply(_Component, [this].concat(args)) || this;
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "dropRef", _react.default.createRef());
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "addScrollListener", function () {
var dropTarget = _this.props.dropTarget;
_this.scrollParents = (0, _utils.findScrollParents)(dropTarget);
_this.scrollParents.forEach(function (scrollParent) {
return scrollParent.addEventListener('scroll', _this.place);
});
});
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "removeScrollListener", function () {
_this.scrollParents.forEach(function (scrollParent) {
return scrollParent.removeEventListener('scroll', _this.place);
});
});
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "onClickDocument", function (event) {
var _this$props = _this.props,
dropTarget = _this$props.dropTarget,
onClickOutside = _this$props.onClickOutside;
var dropTargetNode = dropTarget;
var dropNode = _this.dropRef.current;
if (onClickOutside && dropNode && // need this for ie11
!dropTargetNode.contains(event.target) && !dropNode.contains(event.target)) {
onClickOutside();
}
});
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "onResize", function () {
_this.removeScrollListener();
_this.addScrollListener();
_this.place(false);
});
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "place", function (preserveHeight) {
var _this$props2 = _this.props,
align = _this$props2.align,
dropTarget = _this$props2.dropTarget,
responsive = _this$props2.responsive,
stretch = _this$props2.stretch,
theme = _this$props2.theme;
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var target = dropTarget;
var container = _this.dropRef.current;
if (container && target) {
// clear prior styling
container.style.left = '';
container.style.top = '';
container.style.bottom = '';
container.style.width = '';
if (!preserveHeight) {
container.style.maxHeight = '';
} // get bounds
var targetRect = (0, _utils.findVisibleParent)(target).getBoundingClientRect();
var containerRect = container.getBoundingClientRect(); // determine width
var width = Math.min(stretch ? Math.max(targetRect.width, containerRect.width) : containerRect.width, windowWidth); // set left position
var left;
if (align.left) {
if (align.left === 'left') {
left = targetRect.left;
} else if (align.left === 'right') {
left = targetRect.left + targetRect.width;
}
} else if (align.right) {
if (align.right === 'left') {
left = targetRect.left - width;
} else if (align.right === 'right') {
left = targetRect.left + targetRect.width - width;
}
} else {
left = targetRect.left + targetRect.width / 2 - width / 2;
}
if (left + width > windowWidth) {
left -= left + width - windowWidth;
} else if (left < 0) {
left = 0;
} // set top or bottom position
var top;
var bottom;
var maxHeight = containerRect.height;
if (align.top) {
if (align.top === 'top') {
top = targetRect.top;
} else {
top = targetRect.bottom;
}
maxHeight = windowHeight - top;
} else if (align.bottom) {
if (align.bottom === 'bottom') {
bottom = targetRect.bottom;
} else {
bottom = targetRect.top;
}
maxHeight = bottom;
} else {
// center
top = targetRect.top + targetRect.height / 2 - containerRect.height / 2;
maxHeight = windowHeight - top;
} // if we can't fit it all, or we're rather close,
// see if there's more room the other direction
if (responsive && (containerRect.height > maxHeight || maxHeight < windowHeight / 10)) {
// We need more room than we have.
if (align.top && top > windowHeight / 2) {
// We put it below, but there's more room above, put it above
top = '';
if (align.top === 'bottom') {
// top = Math.max(targetRect.top - containerRect.height, 0);
// maxHeight = targetRect.top - top;
bottom = targetRect.top;
} else {
// top = Math.max(targetRect.bottom - containerRect.height, 0);
// maxHeight = targetRect.bottom - top;
bottom = targetRect.bottom;
}
maxHeight = bottom;
} else if (align.bottom && maxHeight < windowHeight / 2) {
// We put it above but there's more room below, put it below
bottom = '';
if (align.bottom === 'bottom') {
top = targetRect.top;
} else {
top = targetRect.bottom;
}
maxHeight = windowHeight - top;
}
}
container.style.left = left + "px";
if (stretch) {
// offset width by 0.1 to avoid a bug in ie11 that
// unnecessarily wraps the text if width is the same
// NOTE: turned off for now
container.style.width = width + 0.1 + "px";
} // the (position:absolute + scrollTop)
// is presenting issues with desktop scroll flickering
if (top !== '') {
container.style.top = top + "px";
}
if (bottom !== '') {
container.style.bottom = windowHeight - bottom + "px";
}
if (!preserveHeight) {
if (theme.drop && theme.drop.maxHeight) {
maxHeight = Math.min(maxHeight, (0, _utils.parseMetricToNum)(theme.drop.maxHeight));
}
container.style.maxHeight = maxHeight + "px";
}
}
});
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "onEsc", function (event) {
var onEsc = _this.props.onEsc;
event.stopPropagation();
if (onEsc) {
onEsc(event);
}
});
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "preventClickBubbling", function (event) {
event.stopPropagation();
/**
* the React event system actually listens to all events at the top level
* and then handles its own bubbling / capturing virtually. This means that
* even if we call stopPropagation, it only affects the React synthetic
* event, and the native event still bubbles upward.
* Any code that uses native events (like the close listener in this class)
* will still get the bubbled event, unless we also call
* event.nativeEvent.stopImmediatePropagation, which bridges the gap from
* React synthetic event to native DOM event.
*/
event.nativeEvent.stopImmediatePropagation();
});
return _this;
}
var _proto = DropContainer.prototype;
_proto.componentDidMount = function componentDidMount() {
var restrictFocus = this.props.restrictFocus;
this.addScrollListener();
window.addEventListener('resize', this.onResize);
document.addEventListener('mousedown', this.onClickDocument);
this.place(false);
if (restrictFocus) {
this.dropRef.current.focus();
}
};
_proto.componentDidUpdate = function componentDidUpdate() {
this.place(true);
};
_proto.componentWillUnmount = function componentWillUnmount() {
this.removeScrollListener();
window.removeEventListener('resize', this.onResize);
document.removeEventListener('mousedown', this.onClickDocument);
};
_proto.render = function render() {
var _this$props3 = this.props,
alignProp = _this$props3.align,
children = _this$props3.children,
onClickOutside = _this$props3.onClickOutside,
onEsc = _this$props3.onEsc,
onKeyDown = _this$props3.onKeyDown,
elevation = _this$props3.elevation,
plain = _this$props3.plain,
propsTheme = _this$props3.theme,
rest = _objectWithoutPropertiesLoose(_this$props3, ["align", "children", "onClickOutside", "onEsc", "onKeyDown", "elevation", "plain", "theme"]);
var theme = this.context || propsTheme;
var content = _react.default.createElement(_StyledDrop.StyledDrop, _extends({
as: _Box.Box,
plain: plain,
elevation: !plain ? elevation || theme.global.drop.shadowSize || 'small' : undefined,
tabIndex: "-1",
ref: this.dropRef,
alignProp: alignProp,
onMouseDown: this.preventClickBubbling
}, rest), children);
if (theme.global.drop.background) {
var dark = (0, _utils.backgroundIsDark)(theme.global.drop.background, theme);
if (dark !== theme.dark) {
content = _react.default.createElement(_contexts.ThemeContext.Provider, {
value: _extends({}, theme, {
dark: dark
})
}, content);
}
}
return _react.default.createElement(_FocusedContainer.FocusedContainer, {
onKeyDown: onEsc && preventLayerClose
}, _react.default.createElement(_Keyboard.Keyboard, {
onEsc: onEsc && this.onEsc,
onKeyDown: onKeyDown,
target: "document"
}, content));
};
return DropContainer;
}(_react.Component);
exports.DropContainer = DropContainer;
_defineProperty(DropContainer, "contextType", _contexts.ThemeContext);
_defineProperty(DropContainer, "defaultProps", {
align: {
top: 'top',
left: 'left'
},
stretch: 'width'
});
Object.setPrototypeOf(DropContainer.defaultProps, _defaultProps.defaultProps);