UNPKG

21.6 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
6
7var React = require('react');
8var React__default = _interopDefault(React);
9var ReactDOM = _interopDefault(require('react-dom'));
10
11function _extends() {
12 _extends = Object.assign || function (target) {
13 for (var i = 1; i < arguments.length; i++) {
14 var source = arguments[i];
15
16 for (var key in source) {
17 if (Object.prototype.hasOwnProperty.call(source, key)) {
18 target[key] = source[key];
19 }
20 }
21 }
22
23 return target;
24 };
25
26 return _extends.apply(this, arguments);
27}
28
29var useOnEscape = function useOnEscape(handler, active) {
30 if (active === void 0) {
31 active = true;
32 }
33
34 React.useEffect(function () {
35 if (!active) return;
36
37 var listener = function listener(event) {
38 // check if key is an Escape
39 if (event.key === 'Escape') handler(event);
40 };
41
42 document.addEventListener('keyup', listener);
43 return function () {
44 if (!active) return;
45 document.removeEventListener('keyup', listener);
46 };
47 }, [handler, active]);
48};
49var useRepositionOnResize = function useRepositionOnResize(handler, active) {
50 if (active === void 0) {
51 active = true;
52 }
53
54 React.useEffect(function () {
55 if (!active) return;
56
57 var listener = function listener() {
58 handler();
59 };
60
61 window.addEventListener('resize', listener);
62 return function () {
63 if (!active) return;
64 window.removeEventListener('resize', listener);
65 };
66 }, [handler, active]);
67};
68var useOnClickOutside = function useOnClickOutside(ref, handler, active) {
69 if (active === void 0) {
70 active = true;
71 }
72
73 React.useEffect(function () {
74 if (!active) return;
75
76 var listener = function listener(event) {
77 // Do nothing if clicking ref's element or descendent elements
78 var refs = Array.isArray(ref) ? ref : [ref];
79 var contains = false;
80 refs.forEach(function (r) {
81 if (!r.current || r.current.contains(event.target)) {
82 contains = true;
83 return;
84 }
85 });
86 event.stopPropagation();
87 if (!contains) handler(event);
88 };
89
90 document.addEventListener('mousedown', listener);
91 document.addEventListener('touchstart', listener);
92 return function () {
93 if (!active) return;
94 document.removeEventListener('mousedown', listener);
95 document.removeEventListener('touchstart', listener);
96 };
97 }, [ref, handler, active]);
98}; // Make sure that user is not able TAB out of the Modal content on Open
99
100var useTabbing = function useTabbing(contentRef, active) {
101 if (active === void 0) {
102 active = true;
103 }
104
105 React.useEffect(function () {
106 if (!active) return;
107
108 var listener = function listener(event) {
109 // check if key is an Tab
110 if (event.keyCode === 9) {
111 var _contentRef$current;
112
113 var els = contentRef === null || contentRef === void 0 ? void 0 : (_contentRef$current = contentRef.current) === null || _contentRef$current === void 0 ? void 0 : _contentRef$current.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]');
114 var focusableEls = Array.prototype.slice.call(els);
115
116 if (focusableEls.length === 1) {
117 event.preventDefault();
118 return;
119 }
120
121 var firstFocusableEl = focusableEls[0];
122 var lastFocusableEl = focusableEls[focusableEls.length - 1];
123
124 if (event.shiftKey && document.activeElement === firstFocusableEl) {
125 event.preventDefault();
126 lastFocusableEl.focus();
127 } else if (document.activeElement === lastFocusableEl) {
128 event.preventDefault();
129 firstFocusableEl.focus();
130 }
131 }
132 };
133
134 document.addEventListener('keydown', listener);
135 return function () {
136 if (!active) return;
137 document.removeEventListener('keydown', listener);
138 };
139 }, [contentRef, active]);
140};
141var useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
142
143var Style = {
144 popupContent: {
145 tooltip: {
146 position: 'absolute',
147 zIndex: 999
148 },
149 modal: {
150 position: 'relative',
151 margin: 'auto'
152 }
153 },
154 popupArrow: {
155 height: '8px',
156 width: '16px',
157 position: 'absolute',
158 background: 'transparent',
159 color: '#FFF',
160 zIndex: -1
161 },
162 overlay: {
163 tooltip: {
164 position: 'fixed',
165 top: '0',
166 bottom: '0',
167 left: '0',
168 right: '0',
169 zIndex: 999
170 },
171 modal: {
172 position: 'fixed',
173 top: '0',
174 bottom: '0',
175 left: '0',
176 right: '0',
177 display: 'flex',
178 zIndex: 999
179 }
180 }
181};
182
183var POSITION_TYPES = ['top left', 'top center', 'top right', 'right top', 'right center', 'right bottom', 'bottom left', 'bottom center', 'bottom right', 'left top', 'left center', 'left bottom'];
184
185var getCoordinatesForPosition = function getCoordinatesForPosition(triggerBounding, ContentBounding, position, //PopupPosition | PopupPosition[],
186arrow, _ref) {
187 var offsetX = _ref.offsetX,
188 offsetY = _ref.offsetY;
189 var margin = arrow ? 8 : 0;
190 var args = position.split(' '); // the step N 1 : center the popup content => ok
191
192 var CenterTop = triggerBounding.top + triggerBounding.height / 2;
193 var CenterLeft = triggerBounding.left + triggerBounding.width / 2;
194 var height = ContentBounding.height,
195 width = ContentBounding.width;
196 var top = CenterTop - height / 2;
197 var left = CenterLeft - width / 2;
198 var transform = '';
199 var arrowTop = '0%';
200 var arrowLeft = '0%'; // the step N 2 : => ok
201
202 switch (args[0]) {
203 case 'top':
204 top -= height / 2 + triggerBounding.height / 2 + margin;
205 transform = "rotate(180deg) translateX(50%)";
206 arrowTop = '100%';
207 arrowLeft = '50%';
208 break;
209
210 case 'bottom':
211 top += height / 2 + triggerBounding.height / 2 + margin;
212 transform = "rotate(0deg) translateY(-100%) translateX(-50%)";
213 arrowLeft = '50%';
214 break;
215
216 case 'left':
217 left -= width / 2 + triggerBounding.width / 2 + margin;
218 transform = " rotate(90deg) translateY(50%) translateX(-25%)";
219 arrowLeft = '100%';
220 arrowTop = '50%';
221 break;
222
223 case 'right':
224 left += width / 2 + triggerBounding.width / 2 + margin;
225 transform = "rotate(-90deg) translateY(-150%) translateX(25%)";
226 arrowTop = '50%';
227 break;
228 }
229
230 switch (args[1]) {
231 case 'top':
232 top = triggerBounding.top;
233 arrowTop = triggerBounding.height / 2 + "px";
234 break;
235
236 case 'bottom':
237 top = triggerBounding.top - height + triggerBounding.height;
238 arrowTop = height - triggerBounding.height / 2 + "px";
239 break;
240
241 case 'left':
242 left = triggerBounding.left;
243 arrowLeft = triggerBounding.width / 2 + "px";
244 break;
245
246 case 'right':
247 left = triggerBounding.left - width + triggerBounding.width;
248 arrowLeft = width - triggerBounding.width / 2 + "px";
249 break;
250 }
251
252 top = args[0] === 'top' ? top - offsetY : top + offsetY;
253 left = args[0] === 'left' ? left - offsetX : left + offsetX;
254 return {
255 top: top,
256 left: left,
257 transform: transform,
258 arrowLeft: arrowLeft,
259 arrowTop: arrowTop
260 };
261};
262
263var getTooltipBoundary = function getTooltipBoundary(keepTooltipInside) {
264 // add viewport
265 var boundingBox = {
266 top: 0,
267 left: 0,
268
269 /* eslint-disable-next-line no-undef */
270 width: window.innerWidth,
271
272 /* eslint-disable-next-line no-undef */
273 height: window.innerHeight
274 };
275
276 if (typeof keepTooltipInside === 'string') {
277 /* eslint-disable-next-line no-undef */
278 var selector = document.querySelector(keepTooltipInside);
279
280 {
281 if (selector === null) throw new Error(keepTooltipInside + " selector does not exist : keepTooltipInside must be a valid html selector 'class' or 'Id' or a boolean value");
282 }
283
284 if (selector !== null) boundingBox = selector.getBoundingClientRect();
285 }
286
287 return boundingBox;
288};
289
290var calculatePosition = function calculatePosition(triggerBounding, ContentBounding, position, arrow, _ref2, keepTooltipInside) {
291 var offsetX = _ref2.offsetX,
292 offsetY = _ref2.offsetY;
293 var bestCoords = {
294 arrowLeft: '0%',
295 arrowTop: '0%',
296 left: 0,
297 top: 0,
298 transform: 'rotate(135deg)'
299 };
300 var i = 0;
301 var wrapperBox = getTooltipBoundary(keepTooltipInside);
302 var positions = Array.isArray(position) ? position : [position]; // keepTooltipInside would be activated if the keepTooltipInside exist or the position is Array
303
304 if (keepTooltipInside || Array.isArray(position)) positions = [].concat(positions, POSITION_TYPES); // add viewPort for WarpperBox
305 // wrapperBox.top = wrapperBox.top + window.scrollY;
306 // wrapperBox.left = wrapperBox.left + window.scrollX;
307
308 while (i < positions.length) {
309 bestCoords = getCoordinatesForPosition(triggerBounding, ContentBounding, positions[i], arrow, {
310 offsetX: offsetX,
311 offsetY: offsetY
312 });
313 var contentBox = {
314 top: bestCoords.top,
315 left: bestCoords.left,
316 width: ContentBounding.width,
317 height: ContentBounding.height
318 };
319
320 if (contentBox.top <= wrapperBox.top || contentBox.left <= wrapperBox.left || contentBox.top + contentBox.height >= wrapperBox.top + wrapperBox.height || contentBox.left + contentBox.width >= wrapperBox.left + wrapperBox.width) {
321 i++;
322 } else {
323 break;
324 }
325 }
326
327 return bestCoords;
328};
329
330var popupIdCounter = 0;
331
332var getRootPopup = function getRootPopup() {
333 var PopupRoot = document.getElementById('popup-root');
334
335 if (PopupRoot === null) {
336 PopupRoot = document.createElement('div');
337 PopupRoot.setAttribute('id', 'popup-root');
338 document.body.appendChild(PopupRoot);
339 }
340
341 return PopupRoot;
342};
343
344var Popup = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
345 var _ref$trigger = _ref.trigger,
346 trigger = _ref$trigger === void 0 ? null : _ref$trigger,
347 _ref$onOpen = _ref.onOpen,
348 onOpen = _ref$onOpen === void 0 ? function () {} : _ref$onOpen,
349 _ref$onClose = _ref.onClose,
350 onClose = _ref$onClose === void 0 ? function () {} : _ref$onClose,
351 _ref$defaultOpen = _ref.defaultOpen,
352 defaultOpen = _ref$defaultOpen === void 0 ? false : _ref$defaultOpen,
353 _ref$open = _ref.open,
354 open = _ref$open === void 0 ? undefined : _ref$open,
355 _ref$disabled = _ref.disabled,
356 disabled = _ref$disabled === void 0 ? false : _ref$disabled,
357 _ref$nested = _ref.nested,
358 nested = _ref$nested === void 0 ? false : _ref$nested,
359 _ref$closeOnDocumentC = _ref.closeOnDocumentClick,
360 closeOnDocumentClick = _ref$closeOnDocumentC === void 0 ? true : _ref$closeOnDocumentC,
361 _ref$repositionOnResi = _ref.repositionOnResize,
362 repositionOnResize = _ref$repositionOnResi === void 0 ? true : _ref$repositionOnResi,
363 _ref$closeOnEscape = _ref.closeOnEscape,
364 closeOnEscape = _ref$closeOnEscape === void 0 ? true : _ref$closeOnEscape,
365 _ref$on = _ref.on,
366 on = _ref$on === void 0 ? ['click'] : _ref$on,
367 _ref$contentStyle = _ref.contentStyle,
368 contentStyle = _ref$contentStyle === void 0 ? {} : _ref$contentStyle,
369 _ref$arrowStyle = _ref.arrowStyle,
370 arrowStyle = _ref$arrowStyle === void 0 ? {} : _ref$arrowStyle,
371 _ref$overlayStyle = _ref.overlayStyle,
372 overlayStyle = _ref$overlayStyle === void 0 ? {} : _ref$overlayStyle,
373 _ref$className = _ref.className,
374 className = _ref$className === void 0 ? '' : _ref$className,
375 _ref$position = _ref.position,
376 position = _ref$position === void 0 ? 'bottom center' : _ref$position,
377 _ref$modal = _ref.modal,
378 modal = _ref$modal === void 0 ? false : _ref$modal,
379 _ref$lockScroll = _ref.lockScroll,
380 lockScroll = _ref$lockScroll === void 0 ? false : _ref$lockScroll,
381 _ref$arrow = _ref.arrow,
382 arrow = _ref$arrow === void 0 ? true : _ref$arrow,
383 _ref$offsetX = _ref.offsetX,
384 offsetX = _ref$offsetX === void 0 ? 0 : _ref$offsetX,
385 _ref$offsetY = _ref.offsetY,
386 offsetY = _ref$offsetY === void 0 ? 0 : _ref$offsetY,
387 _ref$mouseEnterDelay = _ref.mouseEnterDelay,
388 mouseEnterDelay = _ref$mouseEnterDelay === void 0 ? 100 : _ref$mouseEnterDelay,
389 _ref$mouseLeaveDelay = _ref.mouseLeaveDelay,
390 mouseLeaveDelay = _ref$mouseLeaveDelay === void 0 ? 100 : _ref$mouseLeaveDelay,
391 _ref$keepTooltipInsid = _ref.keepTooltipInside,
392 keepTooltipInside = _ref$keepTooltipInsid === void 0 ? false : _ref$keepTooltipInsid,
393 children = _ref.children;
394
395 var _useState = React.useState(open || defaultOpen),
396 isOpen = _useState[0],
397 setIsOpen = _useState[1];
398
399 var triggerRef = React.useRef(null);
400 var contentRef = React.useRef(null);
401 var arrowRef = React.useRef(null);
402 var focusedElBeforeOpen = React.useRef(null);
403 var popupId = React.useRef("popup-" + ++popupIdCounter);
404 var isModal = modal ? true : !trigger;
405 var timeOut = React.useRef(0);
406 useIsomorphicLayoutEffect(function () {
407 if (isOpen) {
408 focusedElBeforeOpen.current = document.activeElement;
409 setPosition();
410 focusContentOnOpen(); // for accessibility
411
412 lockScrolll();
413 } else {
414 resetScroll();
415 }
416
417 return function () {
418 clearTimeout(timeOut.current);
419 };
420 }, [isOpen]); // for uncontrolled popup we need to sync isOpen with open prop
421
422 React.useEffect(function () {
423 if (typeof open === 'boolean') {
424 if (open) openPopup();else closePopup();
425 }
426 }, [open, disabled]);
427
428 var openPopup = function openPopup(event) {
429 if (isOpen || disabled) return;
430 setIsOpen(true);
431 setTimeout(function () {
432 return onOpen(event);
433 }, 0);
434 };
435
436 var closePopup = function closePopup(event) {
437 var _focusedElBeforeOpen$;
438
439 if (!isOpen || disabled) return;
440 setIsOpen(false);
441 if (isModal) (_focusedElBeforeOpen$ = focusedElBeforeOpen.current) === null || _focusedElBeforeOpen$ === void 0 ? void 0 : _focusedElBeforeOpen$.focus();
442 setTimeout(function () {
443 return onClose(event);
444 }, 0);
445 };
446
447 var togglePopup = function togglePopup(event) {
448 event === null || event === void 0 ? void 0 : event.stopPropagation();
449 if (!isOpen) openPopup(event);else closePopup(event);
450 };
451
452 var onMouseEnter = function onMouseEnter(event) {
453 clearTimeout(timeOut.current);
454 timeOut.current = setTimeout(function () {
455 return openPopup(event);
456 }, mouseEnterDelay);
457 };
458
459 var onContextMenu = function onContextMenu(event) {
460 event === null || event === void 0 ? void 0 : event.preventDefault();
461 togglePopup();
462 };
463
464 var onMouseLeave = function onMouseLeave(event) {
465 clearTimeout(timeOut.current);
466 timeOut.current = setTimeout(function () {
467 return closePopup(event);
468 }, mouseLeaveDelay);
469 };
470
471 var lockScrolll = function lockScrolll() {
472 if (isModal && lockScroll) document.getElementsByTagName('body')[0].style.overflow = 'hidden'; // migrate to document.body
473 };
474
475 var resetScroll = function resetScroll() {
476 if (isModal && lockScroll) document.getElementsByTagName('body')[0].style.overflow = 'auto';
477 };
478
479 var focusContentOnOpen = function focusContentOnOpen() {
480 var _contentRef$current;
481
482 var focusableEls = contentRef === null || contentRef === void 0 ? void 0 : (_contentRef$current = contentRef.current) === null || _contentRef$current === void 0 ? void 0 : _contentRef$current.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]');
483 var firstEl = Array.prototype.slice.call(focusableEls)[0];
484 firstEl === null || firstEl === void 0 ? void 0 : firstEl.focus();
485 };
486
487 React.useImperativeHandle(ref, function () {
488 return {
489 open: function open() {
490 openPopup();
491 },
492 close: function close() {
493 closePopup();
494 },
495 toggle: function toggle() {
496 togglePopup();
497 }
498 };
499 }); // set Position
500
501 var setPosition = function setPosition() {
502 if (isModal || !isOpen) return;
503 if (!(triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.current) || !(triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.current) || !(contentRef === null || contentRef === void 0 ? void 0 : contentRef.current)) return; /// show error as one of ref is undefined
504
505 var trigger = triggerRef.current.getBoundingClientRect();
506 var content = contentRef.current.getBoundingClientRect();
507 var cords = calculatePosition(trigger, content, position, arrow, {
508 offsetX: offsetX,
509 offsetY: offsetY
510 }, keepTooltipInside);
511 contentRef.current.style.top = cords.top + window.scrollY + "px";
512 contentRef.current.style.left = cords.left + window.scrollX + "px";
513
514 if (arrow && !!arrowRef.current) {
515 var _arrowStyle$top, _arrowStyle$left;
516
517 arrowRef.current.style.transform = cords.transform;
518 arrowRef.current.style.setProperty('-ms-transform', cords.transform);
519 arrowRef.current.style.setProperty('-webkit-transform', cords.transform);
520 arrowRef.current.style.top = ((_arrowStyle$top = arrowStyle.top) === null || _arrowStyle$top === void 0 ? void 0 : _arrowStyle$top.toString()) || cords.arrowTop;
521 arrowRef.current.style.left = ((_arrowStyle$left = arrowStyle.left) === null || _arrowStyle$left === void 0 ? void 0 : _arrowStyle$left.toString()) || cords.arrowLeft;
522 }
523 }; // hooks
524
525
526 useOnEscape(closePopup, closeOnEscape); // can be optimized if we disabled for hover
527
528 useTabbing(contentRef, isOpen && isModal);
529 useRepositionOnResize(setPosition, repositionOnResize);
530 useOnClickOutside(!!trigger ? [contentRef, triggerRef] : [contentRef], closePopup, closeOnDocumentClick && !nested); // we need to add a ne
531 // render the trigger element and add events
532
533 var renderTrigger = function renderTrigger() {
534 var triggerProps = {
535 key: 'T',
536 ref: triggerRef,
537 'aria-describedby': popupId.current
538 };
539 var onAsArray = Array.isArray(on) ? on : [on];
540
541 for (var i = 0, len = onAsArray.length; i < len; i++) {
542 switch (onAsArray[i]) {
543 case 'click':
544 triggerProps.onClick = togglePopup;
545 break;
546
547 case 'right-click':
548 triggerProps.onContextMenu = onContextMenu;
549 break;
550
551 case 'hover':
552 triggerProps.onMouseEnter = onMouseEnter;
553 triggerProps.onMouseLeave = onMouseLeave;
554 break;
555
556 case 'focus':
557 triggerProps.onFocus = onMouseEnter;
558 triggerProps.onBlur = onMouseLeave;
559 break;
560 }
561 }
562
563 if (typeof trigger === 'function') {
564 var comp = trigger(isOpen);
565 return !!trigger && React__default.cloneElement(comp, triggerProps);
566 }
567
568 return !!trigger && React__default.cloneElement(trigger, triggerProps);
569 };
570
571 var addWarperAction = function addWarperAction() {
572 var popupContentStyle = isModal ? Style.popupContent.modal : Style.popupContent.tooltip;
573 var childrenElementProps = {
574 className: "popup-content " + (className !== '' ? className.split(' ').map(function (c) {
575 return c + "-content";
576 }).join(' ') : ''),
577 style: _extends({}, popupContentStyle, contentStyle, {
578 pointerEvents: 'auto'
579 }),
580 ref: contentRef,
581 onClick: function onClick(e) {
582 e.stopPropagation();
583 }
584 };
585
586 if (!modal && on.indexOf('hover') >= 0) {
587 childrenElementProps.onMouseEnter = onMouseEnter;
588 childrenElementProps.onMouseLeave = onMouseLeave;
589 }
590
591 return childrenElementProps;
592 };
593
594 var renderContent = function renderContent() {
595 return React__default.createElement("div", Object.assign({}, addWarperAction(), {
596 key: "C",
597 role: isModal ? 'dialog' : 'tooltip',
598 id: popupId.current
599 }), arrow && !isModal && React__default.createElement("div", {
600 ref: arrowRef,
601 style: Style.popupArrow
602 }, React__default.createElement("svg", {
603 "data-testid": "arrow",
604 className: "popup-arrow " + (className !== '' ? className.split(' ').map(function (c) {
605 return c + "-arrow";
606 }).join(' ') : ''),
607 viewBox: "0 0 32 16",
608 style: _extends({
609 position: 'absolute'
610 }, arrowStyle)
611 }, React__default.createElement("path", {
612 d: "M16 0l16 16H0z",
613 fill: "currentcolor"
614 }))), children && typeof children === 'function' ? children(closePopup, isOpen) : children);
615 };
616
617 var overlay = !(on.indexOf('hover') >= 0);
618 var ovStyle = isModal ? Style.overlay.modal : Style.overlay.tooltip;
619 var content = [overlay && React__default.createElement("div", {
620 key: "O",
621 "data-testid": "overlay",
622 "data-popup": isModal ? 'modal' : 'tooltip',
623 className: "popup-overlay " + (className !== '' ? className.split(' ').map(function (c) {
624 return c + "-overlay";
625 }).join(' ') : ''),
626 style: _extends({}, ovStyle, overlayStyle, {
627 pointerEvents: closeOnDocumentClick && nested || isModal ? 'auto' : 'none'
628 }),
629 onClick: closeOnDocumentClick && nested ? closePopup : undefined,
630 tabIndex: -1
631 }, isModal && renderContent()), !isModal && renderContent()];
632 return React__default.createElement(React__default.Fragment, null, renderTrigger(), isOpen && ReactDOM.createPortal(content, getRootPopup()));
633});
634
635exports.Popup = Popup;
636exports.default = Popup;
637//# sourceMappingURL=reactjs-popup.cjs.development.js.map