UNPKG

8.95 kBJavaScriptView Raw
1import _objectWithoutPropertiesLoose from "@babel/runtime-corejs2/helpers/esm/objectWithoutPropertiesLoose";
2import _inheritsLoose from "@babel/runtime-corejs2/helpers/esm/inheritsLoose";
3import _assertThisInitialized from "@babel/runtime-corejs2/helpers/esm/assertThisInitialized";
4import _extends from "@babel/runtime-corejs2/helpers/esm/extends";
5import _Array$isArray from "@babel/runtime-corejs2/core-js/array/is-array";
6import contains from 'dom-helpers/query/contains';
7import React, { cloneElement } from 'react';
8import PropTypes from 'prop-types';
9import ReactDOM from 'react-dom';
10import warning from 'warning';
11import Overlay from './Overlay';
12import createChainedFunction from './utils/createChainedFunction';
13/**
14 * Check if value one is inside or equal to the of value
15 *
16 * @param {string} one
17 * @param {string|array} of
18 * @returns {boolean}
19 */
20
21function isOneOf(one, of) {
22 if (_Array$isArray(of)) {
23 return of.indexOf(one) >= 0;
24 }
25
26 return one === of;
27}
28
29var triggerType = PropTypes.oneOf(['click', 'hover', 'focus']);
30
31var propTypes = _extends({}, Overlay.propTypes, {
32 /**
33 * Specify which action or actions trigger Overlay visibility
34 */
35 trigger: PropTypes.oneOfType([triggerType, PropTypes.arrayOf(triggerType)]),
36
37 /**
38 * A millisecond delay amount to show and hide the Overlay once triggered
39 */
40 delay: PropTypes.number,
41
42 /**
43 * A millisecond delay amount before showing the Overlay once triggered.
44 */
45 delayShow: PropTypes.number,
46
47 /**
48 * A millisecond delay amount before hiding the Overlay once triggered.
49 */
50 delayHide: PropTypes.number,
51 // FIXME: This should be `defaultShow`.
52
53 /**
54 * The initial visibility state of the Overlay. For more nuanced visibility
55 * control, consider using the Overlay component directly.
56 */
57 defaultOverlayShown: PropTypes.bool,
58
59 /**
60 * An element or text to overlay next to the target.
61 */
62 overlay: PropTypes.node.isRequired,
63
64 /**
65 * @private
66 */
67 onBlur: PropTypes.func,
68
69 /**
70 * @private
71 */
72 onClick: PropTypes.func,
73
74 /**
75 * @private
76 */
77 onFocus: PropTypes.func,
78
79 /**
80 * @private
81 */
82 onMouseOut: PropTypes.func,
83
84 /**
85 * @private
86 */
87 onMouseOver: PropTypes.func,
88 // Overridden props from `<Overlay>`.
89
90 /**
91 * @private
92 */
93 target: PropTypes.oneOf([null]),
94
95 /**
96 * @private
97 */
98 onHide: PropTypes.oneOf([null]),
99
100 /**
101 * @private
102 */
103 show: PropTypes.oneOf([null])
104});
105
106var defaultProps = {
107 defaultOverlayShown: false,
108 trigger: ['hover', 'focus']
109};
110
111var OverlayTrigger =
112/*#__PURE__*/
113function (_React$Component) {
114 _inheritsLoose(OverlayTrigger, _React$Component);
115
116 function OverlayTrigger(props, context) {
117 var _this;
118
119 _this = _React$Component.call(this, props, context) || this;
120 _this.handleToggle = _this.handleToggle.bind(_assertThisInitialized(_assertThisInitialized(_this)));
121 _this.handleDelayedShow = _this.handleDelayedShow.bind(_assertThisInitialized(_assertThisInitialized(_this)));
122 _this.handleDelayedHide = _this.handleDelayedHide.bind(_assertThisInitialized(_assertThisInitialized(_this)));
123 _this.handleHide = _this.handleHide.bind(_assertThisInitialized(_assertThisInitialized(_this)));
124
125 _this.handleMouseOver = function (e) {
126 return _this.handleMouseOverOut(_this.handleDelayedShow, e, 'fromElement');
127 };
128
129 _this.handleMouseOut = function (e) {
130 return _this.handleMouseOverOut(_this.handleDelayedHide, e, 'toElement');
131 };
132
133 _this._mountNode = null;
134 _this.state = {
135 show: props.defaultOverlayShown
136 };
137 return _this;
138 }
139
140 var _proto = OverlayTrigger.prototype;
141
142 _proto.componentDidMount = function componentDidMount() {
143 this._mountNode = document.createElement('div');
144 this.renderOverlay();
145 };
146
147 _proto.componentDidUpdate = function componentDidUpdate() {
148 this.renderOverlay();
149 };
150
151 _proto.componentWillUnmount = function componentWillUnmount() {
152 ReactDOM.unmountComponentAtNode(this._mountNode);
153 this._mountNode = null;
154 clearTimeout(this._hoverShowDelay);
155 clearTimeout(this._hoverHideDelay);
156 };
157
158 _proto.handleDelayedHide = function handleDelayedHide() {
159 var _this2 = this;
160
161 if (this._hoverShowDelay != null) {
162 clearTimeout(this._hoverShowDelay);
163 this._hoverShowDelay = null;
164 return;
165 }
166
167 if (!this.state.show || this._hoverHideDelay != null) {
168 return;
169 }
170
171 var delay = this.props.delayHide != null ? this.props.delayHide : this.props.delay;
172
173 if (!delay) {
174 this.hide();
175 return;
176 }
177
178 this._hoverHideDelay = setTimeout(function () {
179 _this2._hoverHideDelay = null;
180
181 _this2.hide();
182 }, delay);
183 };
184
185 _proto.handleDelayedShow = function handleDelayedShow() {
186 var _this3 = this;
187
188 if (this._hoverHideDelay != null) {
189 clearTimeout(this._hoverHideDelay);
190 this._hoverHideDelay = null;
191 return;
192 }
193
194 if (this.state.show || this._hoverShowDelay != null) {
195 return;
196 }
197
198 var delay = this.props.delayShow != null ? this.props.delayShow : this.props.delay;
199
200 if (!delay) {
201 this.show();
202 return;
203 }
204
205 this._hoverShowDelay = setTimeout(function () {
206 _this3._hoverShowDelay = null;
207
208 _this3.show();
209 }, delay);
210 };
211
212 _proto.handleHide = function handleHide() {
213 this.hide();
214 }; // Simple implementation of mouseEnter and mouseLeave.
215 // React's built version is broken: https://github.com/facebook/react/issues/4251
216 // for cases when the trigger is disabled and mouseOut/Over can cause flicker
217 // moving from one child element to another.
218
219
220 _proto.handleMouseOverOut = function handleMouseOverOut(handler, e, relatedNative) {
221 var target = e.currentTarget;
222 var related = e.relatedTarget || e.nativeEvent[relatedNative];
223
224 if ((!related || related !== target) && !contains(target, related)) {
225 handler(e);
226 }
227 };
228
229 _proto.handleToggle = function handleToggle() {
230 if (this.state.show) {
231 this.hide();
232 } else {
233 this.show();
234 }
235 };
236
237 _proto.hide = function hide() {
238 this.setState({
239 show: false
240 });
241 };
242
243 _proto.makeOverlay = function makeOverlay(overlay, props) {
244 return React.createElement(Overlay, _extends({}, props, {
245 show: this.state.show,
246 onHide: this.handleHide,
247 target: this
248 }), overlay);
249 };
250
251 _proto.show = function show() {
252 this.setState({
253 show: true
254 });
255 };
256
257 _proto.renderOverlay = function renderOverlay() {
258 ReactDOM.unstable_renderSubtreeIntoContainer(this, this._overlay, this._mountNode);
259 };
260
261 _proto.render = function render() {
262 var _this$props = this.props,
263 trigger = _this$props.trigger,
264 overlay = _this$props.overlay,
265 children = _this$props.children,
266 onBlur = _this$props.onBlur,
267 onClick = _this$props.onClick,
268 onFocus = _this$props.onFocus,
269 onMouseOut = _this$props.onMouseOut,
270 onMouseOver = _this$props.onMouseOver,
271 props = _objectWithoutPropertiesLoose(_this$props, ["trigger", "overlay", "children", "onBlur", "onClick", "onFocus", "onMouseOut", "onMouseOver"]);
272
273 delete props.delay;
274 delete props.delayShow;
275 delete props.delayHide;
276 delete props.defaultOverlayShown;
277 var child = React.Children.only(children);
278 var childProps = child.props;
279 var triggerProps = {};
280
281 if (this.state.show) {
282 triggerProps['aria-describedby'] = overlay.props.id;
283 } // FIXME: The logic here for passing through handlers on this component is
284 // inconsistent. We shouldn't be passing any of these props through.
285
286
287 triggerProps.onClick = createChainedFunction(childProps.onClick, onClick);
288
289 if (isOneOf('click', trigger)) {
290 triggerProps.onClick = createChainedFunction(triggerProps.onClick, this.handleToggle);
291 }
292
293 if (isOneOf('hover', trigger)) {
294 process.env.NODE_ENV !== "production" ? warning(!(trigger === 'hover'), '[react-bootstrap] Specifying only the `"hover"` trigger limits the ' + 'visibility of the overlay to just mouse users. Consider also ' + 'including the `"focus"` trigger so that touch and keyboard only ' + 'users can see the overlay as well.') : void 0;
295 triggerProps.onMouseOver = createChainedFunction(childProps.onMouseOver, onMouseOver, this.handleMouseOver);
296 triggerProps.onMouseOut = createChainedFunction(childProps.onMouseOut, onMouseOut, this.handleMouseOut);
297 }
298
299 if (isOneOf('focus', trigger)) {
300 triggerProps.onFocus = createChainedFunction(childProps.onFocus, onFocus, this.handleDelayedShow);
301 triggerProps.onBlur = createChainedFunction(childProps.onBlur, onBlur, this.handleDelayedHide);
302 }
303
304 this._overlay = this.makeOverlay(overlay, props);
305 return cloneElement(child, triggerProps);
306 };
307
308 return OverlayTrigger;
309}(React.Component);
310
311OverlayTrigger.propTypes = propTypes;
312OverlayTrigger.defaultProps = defaultProps;
313export default OverlayTrigger;
\No newline at end of file