@atlaskit/teams-app-internal-popup-adaptor 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +17 -0
  3. package/__tests__/unit/PopupTriggerWithHover.test.tsx +141 -0
  4. package/__tests__/unit/useHoverDelay.test.tsx +99 -0
  5. package/__tests__/unit/useHoverTriggerRef.test.tsx +121 -0
  6. package/__tests__/unit/usePreloadRef.test.tsx +118 -0
  7. package/__tests__/unit/usePressableTriggerRef.test.tsx +104 -0
  8. package/__tests__/unit/utils.test.tsx +86 -0
  9. package/afm-cc/tsconfig.json +40 -0
  10. package/afm-products/tsconfig.json +40 -0
  11. package/dist/cjs/PopupTriggerWithHover.js +158 -0
  12. package/dist/cjs/index.js +12 -0
  13. package/dist/cjs/useHoverDelay.js +38 -0
  14. package/dist/cjs/useHoverTriggerRef.js +155 -0
  15. package/dist/cjs/usePreloadRef.js +119 -0
  16. package/dist/cjs/usePressableTriggerRef.js +69 -0
  17. package/dist/cjs/utils.js +48 -0
  18. package/dist/es2019/PopupTriggerWithHover.js +139 -0
  19. package/dist/es2019/index.js +1 -0
  20. package/dist/es2019/useHoverDelay.js +32 -0
  21. package/dist/es2019/useHoverTriggerRef.js +139 -0
  22. package/dist/es2019/usePreloadRef.js +115 -0
  23. package/dist/es2019/usePressableTriggerRef.js +62 -0
  24. package/dist/es2019/utils.js +40 -0
  25. package/dist/esm/PopupTriggerWithHover.js +149 -0
  26. package/dist/esm/index.js +1 -0
  27. package/dist/esm/useHoverDelay.js +34 -0
  28. package/dist/esm/useHoverTriggerRef.js +149 -0
  29. package/dist/esm/usePreloadRef.js +113 -0
  30. package/dist/esm/usePressableTriggerRef.js +63 -0
  31. package/dist/esm/utils.js +41 -0
  32. package/dist/types/PopupTriggerWithHover.d.ts +28 -0
  33. package/dist/types/index.d.ts +1 -0
  34. package/dist/types/useHoverDelay.d.ts +8 -0
  35. package/dist/types/useHoverTriggerRef.d.ts +19 -0
  36. package/dist/types/usePreloadRef.d.ts +13 -0
  37. package/dist/types/usePressableTriggerRef.d.ts +9 -0
  38. package/dist/types/utils.d.ts +16 -0
  39. package/dist/types-ts4.5/PopupTriggerWithHover.d.ts +28 -0
  40. package/dist/types-ts4.5/index.d.ts +1 -0
  41. package/dist/types-ts4.5/useHoverDelay.d.ts +8 -0
  42. package/dist/types-ts4.5/useHoverTriggerRef.d.ts +19 -0
  43. package/dist/types-ts4.5/usePreloadRef.d.ts +13 -0
  44. package/dist/types-ts4.5/usePressableTriggerRef.d.ts +9 -0
  45. package/dist/types-ts4.5/utils.d.ts +16 -0
  46. package/package.json +81 -0
  47. package/popup-trigger-with-hover/package.json +17 -0
  48. package/src/PopupTriggerWithHover.tsx +240 -0
  49. package/src/index.ts +5 -0
  50. package/src/useHoverDelay.ts +42 -0
  51. package/src/useHoverTriggerRef.ts +177 -0
  52. package/src/usePreloadRef.ts +152 -0
  53. package/src/usePressableTriggerRef.ts +89 -0
  54. package/src/utils.ts +49 -0
  55. package/tsconfig.app.json +46 -0
  56. package/tsconfig.dev.json +45 -0
  57. package/tsconfig.json +19 -0
@@ -0,0 +1,139 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
3
+ import { loadEntryPoint } from 'react-relay';
4
+ import { mergeRefs } from 'use-callback-ref';
5
+ import { Popup } from '@atlaskit/popup';
6
+ import Spinner from '@atlaskit/spinner';
7
+ import { InternalEntryPointContainer } from '@atlassian/internal-entry-point-container';
8
+ import { useRelayEnvironmentProvider } from '@atlassian/relay-environment-provider';
9
+ import { useHoverTriggerRef } from './useHoverTriggerRef';
10
+ import { usePreloadRef } from './usePreloadRef';
11
+ import { usePressableTriggerRef } from './usePressableTriggerRef';
12
+ const emptyEntryPointParams = {};
13
+ const emptyEntryPointProps = {};
14
+ const FallbackComponent = ({
15
+ fallback,
16
+ onUnmount
17
+ }) => {
18
+ useEffect(() => {
19
+ return () => onUnmount();
20
+ }, [onUnmount]);
21
+ return /*#__PURE__*/React.createElement(React.Fragment, null, fallback || /*#__PURE__*/React.createElement(Spinner, null));
22
+ };
23
+ export function PopupTriggerWithHover({
24
+ trigger,
25
+ triggerMode = 'click',
26
+ entryPoint,
27
+ entryPointParams,
28
+ entryPointProps,
29
+ fallback,
30
+ errorFallback,
31
+ onError,
32
+ forcedReportErrorUFO,
33
+ onOpen,
34
+ onClose,
35
+ ...popupProps
36
+ }) {
37
+ const environmentProvider = useRelayEnvironmentProvider();
38
+ const id = useMemo(() => {
39
+ return entryPoint.root.getModuleName() || 'unknown';
40
+ }, [entryPoint]);
41
+ const [isOpen, setIsOpen] = useState(false);
42
+ const [content, setContent] = useState(null);
43
+ const updateRef = useRef(null);
44
+ const openedByRef = useRef(null);
45
+ const isOpenRef = useRef(isOpen);
46
+ const triggers = Array.isArray(triggerMode) ? triggerMode : [triggerMode];
47
+ useLayoutEffect(() => {
48
+ isOpenRef.current = isOpen;
49
+ }, [isOpen]);
50
+ const handleOpen = useCallback(method => {
51
+ setIsOpen(true);
52
+ if (method) {
53
+ onOpen === null || onOpen === void 0 ? void 0 : onOpen(method);
54
+ }
55
+ }, [onOpen]);
56
+ const handleClose = useCallback((event, currentLevel) => {
57
+ setIsOpen(false);
58
+ onClose === null || onClose === void 0 ? void 0 : onClose(event !== null && event !== void 0 ? event : null, currentLevel);
59
+ }, [onClose]);
60
+ const load = useCallback(() => loadEntryPoint(environmentProvider, entryPoint, entryPointParams !== null && entryPointParams !== void 0 ? entryPointParams : emptyEntryPointParams), [entryPoint, entryPointParams, environmentProvider]);
61
+ const onLoad = useCallback(({
62
+ reference: entryPointReference
63
+ }) => {
64
+ if (isOpenRef.current) {
65
+ return;
66
+ }
67
+ handleOpen(openedByRef.current);
68
+ setContent( /*#__PURE__*/React.createElement(InternalEntryPointContainer, {
69
+ id: id,
70
+ entryPointReference: entryPointReference,
71
+ fallback: /*#__PURE__*/React.createElement(FallbackComponent, {
72
+ fallback: fallback,
73
+ onUnmount: () => {
74
+ var _updateRef$current;
75
+ return (_updateRef$current = updateRef.current) === null || _updateRef$current === void 0 ? void 0 : _updateRef$current.call(updateRef);
76
+ }
77
+ }),
78
+ errorFallback: errorFallback,
79
+ runtimeProps: {
80
+ ...(entryPointProps !== null && entryPointProps !== void 0 ? entryPointProps : emptyEntryPointProps),
81
+ onContentResized: () => {
82
+ var _updateRef$current2;
83
+ (_updateRef$current2 = updateRef.current) === null || _updateRef$current2 === void 0 ? void 0 : _updateRef$current2.call(updateRef);
84
+ },
85
+ onClose: handleClose
86
+ },
87
+ onError: onError,
88
+ forcedError: forcedReportErrorUFO
89
+ }));
90
+ }, [setContent, id, entryPointProps, fallback, forcedReportErrorUFO, errorFallback, handleOpen, onError, handleClose]);
91
+ const preloadRef = usePreloadRef({
92
+ load,
93
+ onLoad
94
+ });
95
+ const pressableRef = usePressableTriggerRef({
96
+ onOpen: preloadRef.loadAndOpen,
97
+ onClose: handleClose,
98
+ isOpen,
99
+ isDisabled: !triggers.includes('click'),
100
+ openedByRef
101
+ });
102
+ const {
103
+ triggerRef: hoverTriggerRef,
104
+ contentRef: hoverContentRef
105
+ } = useHoverTriggerRef({
106
+ onOpen: preloadRef.loadAndOpen,
107
+ onClose: handleClose,
108
+ isOpen,
109
+ isDisabled: !triggers.includes('hover'),
110
+ openedByRef
111
+ });
112
+ useEffect(() => {
113
+ if (!isOpen) {
114
+ openedByRef.current = null;
115
+ }
116
+ }, [isOpen]);
117
+ const popupTrigger = useCallback(triggerProps => {
118
+ const mergedRef = mergeRefs([preloadRef, pressableRef, hoverTriggerRef, triggerProps.ref]);
119
+ return trigger({
120
+ ...triggerProps,
121
+ ref: mergedRef
122
+ });
123
+ }, [trigger, preloadRef, pressableRef, hoverTriggerRef]);
124
+ const popupContent = useCallback(({
125
+ update
126
+ }) => {
127
+ updateRef.current = update;
128
+ return /*#__PURE__*/React.createElement("div", {
129
+ ref: hoverContentRef,
130
+ role: "none"
131
+ }, content);
132
+ }, [content, hoverContentRef]);
133
+ return /*#__PURE__*/React.createElement(Popup, _extends({}, popupProps, {
134
+ isOpen: isOpen,
135
+ onClose: handleClose,
136
+ trigger: popupTrigger,
137
+ content: popupContent
138
+ }));
139
+ }
@@ -0,0 +1 @@
1
+ export { PopupTriggerWithHover } from './PopupTriggerWithHover';
@@ -0,0 +1,32 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+
3
+ /**
4
+ * Hook for managing delayed hover actions with timeout management.
5
+ */
6
+ function useHoverDelay(callback, delay) {
7
+ const timeoutRef = useRef(null);
8
+ const callbackRef = useRef(callback);
9
+ useEffect(() => {
10
+ callbackRef.current = callback;
11
+ }, [callback]);
12
+ const clear = useCallback(() => {
13
+ if (timeoutRef.current) {
14
+ clearTimeout(timeoutRef.current);
15
+ timeoutRef.current = null;
16
+ }
17
+ }, []);
18
+ const schedule = useCallback(() => {
19
+ clear();
20
+ timeoutRef.current = window.setTimeout(() => {
21
+ callbackRef.current();
22
+ }, delay);
23
+ }, [delay, clear]);
24
+ useEffect(() => {
25
+ return () => clear();
26
+ }, [clear]);
27
+ return {
28
+ schedule,
29
+ clear
30
+ };
31
+ }
32
+ export { useHoverDelay };
@@ -0,0 +1,139 @@
1
+ import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
2
+ import { bind } from 'bind-event-listener';
3
+ import { useHoverDelay } from './useHoverDelay';
4
+ import { getChildPortalFromParentPopup, hasRelatedTarget, isMovingToKudos } from './utils';
5
+ const HOVER_OPEN_DELAY = 800;
6
+ const HOVER_CLOSE_DELAY = 200;
7
+ /**
8
+ * Hover behavior for popup trigger and content with delayed open/close.
9
+ */
10
+ export function useHoverTriggerRef({
11
+ onOpen,
12
+ onClose,
13
+ isOpen = false,
14
+ isDisabled = false,
15
+ openedByRef,
16
+ delays = {}
17
+ }) {
18
+ const {
19
+ hoverOpen = HOVER_OPEN_DELAY,
20
+ hoverClose = HOVER_CLOSE_DELAY
21
+ } = delays;
22
+ const triggerNodeRef = useRef(null);
23
+ const triggerListeners = useRef([]);
24
+ const contentNodeRef = useRef(null);
25
+ const contentListeners = useRef(new Map());
26
+ const isOpenRef = useRef(isOpen);
27
+ useLayoutEffect(() => {
28
+ isOpenRef.current = isOpen;
29
+ }, [isOpen]);
30
+ const openDelay = useHoverDelay(() => {
31
+ if (isOpenRef.current) {
32
+ return;
33
+ }
34
+ if (openedByRef) {
35
+ openedByRef.current = 'hover';
36
+ }
37
+ onOpen();
38
+ }, hoverOpen);
39
+ const closeDelay = useHoverDelay(() => {
40
+ if (!isOpenRef.current) {
41
+ return;
42
+ }
43
+ if (!openedByRef || openedByRef.current === 'hover') {
44
+ onClose === null || onClose === void 0 ? void 0 : onClose();
45
+ }
46
+ }, hoverClose);
47
+ const openDelayRef = useRef(openDelay);
48
+ const closeDelayRef = useRef(closeDelay);
49
+ openDelayRef.current = openDelay;
50
+ closeDelayRef.current = closeDelay;
51
+ const handleTriggerEnter = useCallback(() => {
52
+ closeDelayRef.current.clear();
53
+ openDelayRef.current.schedule();
54
+ }, []);
55
+ const handleTriggerLeave = useCallback(() => {
56
+ openDelayRef.current.clear();
57
+ closeDelayRef.current.schedule();
58
+ }, []);
59
+ const triggerRef = useCallback(element => {
60
+ if (element === triggerNodeRef.current) {
61
+ return;
62
+ }
63
+ triggerListeners.current.forEach(unbind => unbind());
64
+ triggerListeners.current = [];
65
+ triggerNodeRef.current = element;
66
+ if (element && !isDisabled) {
67
+ triggerListeners.current.push(bind(element, {
68
+ type: 'mouseenter',
69
+ listener: handleTriggerEnter
70
+ }), bind(element, {
71
+ type: 'mouseleave',
72
+ listener: handleTriggerLeave
73
+ }));
74
+ }
75
+ }, [handleTriggerEnter, handleTriggerLeave, isDisabled]);
76
+ const handleContentEnter = useCallback(() => {
77
+ closeDelayRef.current.clear();
78
+ }, []);
79
+ const cleanupContentListeners = useCallback(() => {
80
+ contentListeners.current.forEach(unbind => unbind());
81
+ contentListeners.current.clear();
82
+ }, []);
83
+ const attachContentListeners = useCallback(element => {
84
+ if (contentListeners.current.has(element)) {
85
+ return;
86
+ }
87
+ const handleMouseLeave = event => {
88
+ if (!hasRelatedTarget(event)) {
89
+ return;
90
+ }
91
+ const relatedTarget = event.relatedTarget;
92
+ if (isMovingToKudos(relatedTarget)) {
93
+ return;
94
+ }
95
+ const childPortal = getChildPortalFromParentPopup(relatedTarget, contentNodeRef.current);
96
+ if (childPortal) {
97
+ attachContentListeners(childPortal);
98
+ return;
99
+ }
100
+ closeDelayRef.current.schedule();
101
+ };
102
+ const unbindEnter = bind(element, {
103
+ type: 'mouseenter',
104
+ listener: handleContentEnter
105
+ });
106
+ const unbindLeave = bind(element, {
107
+ type: 'mouseleave',
108
+ listener: handleMouseLeave
109
+ });
110
+ contentListeners.current.set(element, () => {
111
+ unbindEnter();
112
+ unbindLeave();
113
+ });
114
+ }, [handleContentEnter]);
115
+ const contentRef = useCallback(element => {
116
+ if (element === contentNodeRef.current) {
117
+ return;
118
+ }
119
+ cleanupContentListeners();
120
+ contentNodeRef.current = element;
121
+ if (element && !isDisabled) {
122
+ attachContentListeners(element);
123
+ }
124
+ }, [attachContentListeners, cleanupContentListeners, isDisabled]);
125
+ useEffect(() => {
126
+ const listeners = contentListeners.current;
127
+ const triggers = triggerListeners.current;
128
+ return () => {
129
+ openDelayRef.current.clear();
130
+ closeDelayRef.current.clear();
131
+ triggers.forEach(unbind => unbind());
132
+ listeners.forEach(unbind => unbind());
133
+ };
134
+ }, []);
135
+ return {
136
+ triggerRef,
137
+ contentRef
138
+ };
139
+ }
@@ -0,0 +1,115 @@
1
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
2
+ import { bind } from 'bind-event-listener';
3
+ const PRELOAD_MAX_AGE = 5 * 60 * 1000;
4
+ const TIME_TO_INTENT = 200;
5
+ export function usePreloadRef({
6
+ load,
7
+ onLoad
8
+ }) {
9
+ const nodeRef = useRef(null);
10
+ const eventListeners = useRef([]);
11
+ const {
12
+ loadAndOpen,
13
+ preloadReference,
14
+ cancelPreload
15
+ } = useMemo(() => {
16
+ const request = {};
17
+ const clearPreloadTimeouts = () => {
18
+ if (request.preloadIntentTimeout != null) {
19
+ clearTimeout(request.preloadIntentTimeout);
20
+ delete request.preloadIntentTimeout;
21
+ }
22
+ if (request.preloadMaxAgeTimeout != null) {
23
+ clearTimeout(request.preloadMaxAgeTimeout);
24
+ delete request.preloadMaxAgeTimeout;
25
+ }
26
+ };
27
+ const cancelPreload = () => {
28
+ var _request$preload;
29
+ clearPreloadTimeouts();
30
+ (_request$preload = request.preload) === null || _request$preload === void 0 ? void 0 : _request$preload.dispose();
31
+ };
32
+ const loadReference = () => {
33
+ var _request$preload$refe, _request$preload2;
34
+ if (request.load) {
35
+ return request.load;
36
+ }
37
+ const reference = (_request$preload$refe = (_request$preload2 = request.preload) === null || _request$preload2 === void 0 ? void 0 : _request$preload2.reference) !== null && _request$preload$refe !== void 0 ? _request$preload$refe : load();
38
+ if (request.preload) {
39
+ delete request.preload;
40
+ }
41
+ request.load = {
42
+ reference,
43
+ dispose() {
44
+ reference.dispose();
45
+ delete request.load;
46
+ }
47
+ };
48
+ return request.load;
49
+ };
50
+ const loadAndOpen = () => {
51
+ clearPreloadTimeouts();
52
+ onLoad(loadReference());
53
+ };
54
+ const preloadReference = () => {
55
+ if (request.preloadIntentTimeout || request.preload) {
56
+ return;
57
+ }
58
+ request.preloadIntentTimeout = setTimeout(() => {
59
+ delete request.preloadIntentTimeout;
60
+ const reference = load();
61
+ request.preload = {
62
+ reference,
63
+ dispose() {
64
+ reference.dispose();
65
+ delete request.preload;
66
+ }
67
+ };
68
+ request.preloadMaxAgeTimeout = setTimeout(() => {
69
+ var _request$preload3;
70
+ delete request.preloadMaxAgeTimeout;
71
+ (_request$preload3 = request.preload) === null || _request$preload3 === void 0 ? void 0 : _request$preload3.dispose();
72
+ }, PRELOAD_MAX_AGE);
73
+ }, TIME_TO_INTENT);
74
+ };
75
+ return {
76
+ loadAndOpen,
77
+ preloadReference,
78
+ cancelPreload
79
+ };
80
+ }, [load, onLoad]);
81
+ const cleanUpEventListeners = useCallback(() => {
82
+ eventListeners.current.forEach(unbind => {
83
+ unbind();
84
+ });
85
+ eventListeners.current = [];
86
+ }, []);
87
+ const addEventListener = useCallback((element, type, callback) => {
88
+ const unbind = bind(element, {
89
+ type,
90
+ listener: callback
91
+ });
92
+ eventListeners.current.push(unbind);
93
+ }, []);
94
+ const refCallback = useCallback(element => {
95
+ if (element !== nodeRef.current) {
96
+ if (nodeRef.current) {
97
+ cancelPreload();
98
+ cleanUpEventListeners();
99
+ }
100
+ if (element) {
101
+ addEventListener(element, 'mouseenter', preloadReference);
102
+ addEventListener(element, 'mouseleave', cancelPreload);
103
+ }
104
+ nodeRef.current = element;
105
+ }
106
+ }, [preloadReference, cancelPreload, addEventListener, cleanUpEventListeners]);
107
+ useEffect(() => {
108
+ return () => {
109
+ cleanUpEventListeners();
110
+ };
111
+ }, [cleanUpEventListeners]);
112
+ return Object.assign(refCallback, {
113
+ loadAndOpen
114
+ });
115
+ }
@@ -0,0 +1,62 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import { bind } from 'bind-event-listener';
3
+ export function usePressableTriggerRef({
4
+ onOpen,
5
+ onClose,
6
+ isOpen = false,
7
+ isDisabled = false,
8
+ openedByRef
9
+ }) {
10
+ const nodeRef = useRef(null);
11
+ const eventListeners = useRef([]);
12
+ const handleOpen = useCallback(() => {
13
+ if (isOpen) {
14
+ onClose === null || onClose === void 0 ? void 0 : onClose();
15
+ } else {
16
+ if (openedByRef) {
17
+ openedByRef.current = 'click';
18
+ }
19
+ onOpen();
20
+ }
21
+ }, [isOpen, onClose, openedByRef, onOpen]);
22
+ const keydownListener = useCallback(event => {
23
+ if (event instanceof KeyboardEvent && (event.key === 'Enter' || event.key === ' ')) {
24
+ event.preventDefault();
25
+ handleOpen();
26
+ }
27
+ }, [handleOpen]);
28
+ const cleanUpEventListeners = useCallback(() => {
29
+ eventListeners.current.forEach(unbind => {
30
+ unbind();
31
+ });
32
+ eventListeners.current = [];
33
+ }, []);
34
+ const addEventListener = useCallback((element, type, callback) => {
35
+ const unbind = bind(element, {
36
+ type,
37
+ listener: callback
38
+ });
39
+ eventListeners.current.push(unbind);
40
+ }, []);
41
+ const refCallback = useCallback(element => {
42
+ if (element !== nodeRef.current) {
43
+ if (nodeRef.current) {
44
+ cleanUpEventListeners();
45
+ }
46
+ if (element) {
47
+ if (!isDisabled) {
48
+ addEventListener(element, 'click', handleOpen);
49
+ }
50
+ // Always attach keydown for keyboard accessibility
51
+ addEventListener(element, 'keydown', keydownListener);
52
+ }
53
+ nodeRef.current = element;
54
+ }
55
+ }, [handleOpen, keydownListener, addEventListener, cleanUpEventListeners, isDisabled]);
56
+ useEffect(() => {
57
+ return () => {
58
+ cleanUpEventListeners();
59
+ };
60
+ }, [cleanUpEventListeners]);
61
+ return refCallback;
62
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Type guard to check if an event has a relatedTarget property.
3
+ */
4
+ function hasRelatedTarget(event) {
5
+ return 'relatedTarget' in event;
6
+ }
7
+
8
+ /**
9
+ * Checks if the mouse is moving to an atlaskit portal or kudos modal
10
+ * where we should prevent the popup from closing
11
+ */
12
+ function isMovingToKudos(relatedTarget) {
13
+ var _relatedTarget$closes;
14
+ if (!(relatedTarget instanceof HTMLElement)) {
15
+ return false;
16
+ }
17
+ const iframe = ((_relatedTarget$closes = relatedTarget.closest('.atlaskit-portal')) === null || _relatedTarget$closes === void 0 ? void 0 : _relatedTarget$closes.querySelector('iframe')) || relatedTarget.closest('iframe');
18
+ return iframe instanceof HTMLIFrameElement && iframe.src.includes('give-kudos');
19
+ }
20
+
21
+ /**
22
+ * Returns the child portal if mouse is moving to a portal controlled by the parent popup.
23
+ */
24
+ function getChildPortalFromParentPopup(relatedTarget, parentPopupElement) {
25
+ if (!(relatedTarget instanceof HTMLElement) || !parentPopupElement) {
26
+ return null;
27
+ }
28
+ const portal = relatedTarget.closest('.atlaskit-portal');
29
+ if (!portal) {
30
+ return null;
31
+ }
32
+ for (const trigger of Array.from(parentPopupElement.querySelectorAll('[aria-controls]'))) {
33
+ const controlsId = trigger.getAttribute('aria-controls');
34
+ if (controlsId && portal.querySelector(`#${controlsId}`)) {
35
+ return portal;
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+ export { hasRelatedTarget, isMovingToKudos, getChildPortalFromParentPopup };
@@ -0,0 +1,149 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
4
+ import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
5
+ var _excluded = ["trigger", "triggerMode", "entryPoint", "entryPointParams", "entryPointProps", "fallback", "errorFallback", "onError", "forcedReportErrorUFO", "onOpen", "onClose"];
6
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
7
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
8
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
9
+ import { loadEntryPoint } from 'react-relay';
10
+ import { mergeRefs } from 'use-callback-ref';
11
+ import { Popup } from '@atlaskit/popup';
12
+ import Spinner from '@atlaskit/spinner';
13
+ import { InternalEntryPointContainer } from '@atlassian/internal-entry-point-container';
14
+ import { useRelayEnvironmentProvider } from '@atlassian/relay-environment-provider';
15
+ import { useHoverTriggerRef } from './useHoverTriggerRef';
16
+ import { usePreloadRef } from './usePreloadRef';
17
+ import { usePressableTriggerRef } from './usePressableTriggerRef';
18
+ var emptyEntryPointParams = {};
19
+ var emptyEntryPointProps = {};
20
+ var FallbackComponent = function FallbackComponent(_ref) {
21
+ var fallback = _ref.fallback,
22
+ onUnmount = _ref.onUnmount;
23
+ useEffect(function () {
24
+ return function () {
25
+ return onUnmount();
26
+ };
27
+ }, [onUnmount]);
28
+ return /*#__PURE__*/React.createElement(React.Fragment, null, fallback || /*#__PURE__*/React.createElement(Spinner, null));
29
+ };
30
+ export function PopupTriggerWithHover(_ref2) {
31
+ var trigger = _ref2.trigger,
32
+ _ref2$triggerMode = _ref2.triggerMode,
33
+ triggerMode = _ref2$triggerMode === void 0 ? 'click' : _ref2$triggerMode,
34
+ entryPoint = _ref2.entryPoint,
35
+ entryPointParams = _ref2.entryPointParams,
36
+ entryPointProps = _ref2.entryPointProps,
37
+ fallback = _ref2.fallback,
38
+ errorFallback = _ref2.errorFallback,
39
+ onError = _ref2.onError,
40
+ forcedReportErrorUFO = _ref2.forcedReportErrorUFO,
41
+ onOpen = _ref2.onOpen,
42
+ onClose = _ref2.onClose,
43
+ popupProps = _objectWithoutProperties(_ref2, _excluded);
44
+ var environmentProvider = useRelayEnvironmentProvider();
45
+ var id = useMemo(function () {
46
+ return entryPoint.root.getModuleName() || 'unknown';
47
+ }, [entryPoint]);
48
+ var _useState = useState(false),
49
+ _useState2 = _slicedToArray(_useState, 2),
50
+ isOpen = _useState2[0],
51
+ setIsOpen = _useState2[1];
52
+ var _useState3 = useState(null),
53
+ _useState4 = _slicedToArray(_useState3, 2),
54
+ content = _useState4[0],
55
+ setContent = _useState4[1];
56
+ var updateRef = useRef(null);
57
+ var openedByRef = useRef(null);
58
+ var isOpenRef = useRef(isOpen);
59
+ var triggers = Array.isArray(triggerMode) ? triggerMode : [triggerMode];
60
+ useLayoutEffect(function () {
61
+ isOpenRef.current = isOpen;
62
+ }, [isOpen]);
63
+ var handleOpen = useCallback(function (method) {
64
+ setIsOpen(true);
65
+ if (method) {
66
+ onOpen === null || onOpen === void 0 || onOpen(method);
67
+ }
68
+ }, [onOpen]);
69
+ var handleClose = useCallback(function (event, currentLevel) {
70
+ setIsOpen(false);
71
+ onClose === null || onClose === void 0 || onClose(event !== null && event !== void 0 ? event : null, currentLevel);
72
+ }, [onClose]);
73
+ var load = useCallback(function () {
74
+ return loadEntryPoint(environmentProvider, entryPoint, entryPointParams !== null && entryPointParams !== void 0 ? entryPointParams : emptyEntryPointParams);
75
+ }, [entryPoint, entryPointParams, environmentProvider]);
76
+ var onLoad = useCallback(function (_ref3) {
77
+ var entryPointReference = _ref3.reference;
78
+ if (isOpenRef.current) {
79
+ return;
80
+ }
81
+ handleOpen(openedByRef.current);
82
+ setContent( /*#__PURE__*/React.createElement(InternalEntryPointContainer, {
83
+ id: id,
84
+ entryPointReference: entryPointReference,
85
+ fallback: /*#__PURE__*/React.createElement(FallbackComponent, {
86
+ fallback: fallback,
87
+ onUnmount: function onUnmount() {
88
+ var _updateRef$current;
89
+ return (_updateRef$current = updateRef.current) === null || _updateRef$current === void 0 ? void 0 : _updateRef$current.call(updateRef);
90
+ }
91
+ }),
92
+ errorFallback: errorFallback,
93
+ runtimeProps: _objectSpread(_objectSpread({}, entryPointProps !== null && entryPointProps !== void 0 ? entryPointProps : emptyEntryPointProps), {}, {
94
+ onContentResized: function onContentResized() {
95
+ var _updateRef$current2;
96
+ (_updateRef$current2 = updateRef.current) === null || _updateRef$current2 === void 0 || _updateRef$current2.call(updateRef);
97
+ },
98
+ onClose: handleClose
99
+ }),
100
+ onError: onError,
101
+ forcedError: forcedReportErrorUFO
102
+ }));
103
+ }, [setContent, id, entryPointProps, fallback, forcedReportErrorUFO, errorFallback, handleOpen, onError, handleClose]);
104
+ var preloadRef = usePreloadRef({
105
+ load: load,
106
+ onLoad: onLoad
107
+ });
108
+ var pressableRef = usePressableTriggerRef({
109
+ onOpen: preloadRef.loadAndOpen,
110
+ onClose: handleClose,
111
+ isOpen: isOpen,
112
+ isDisabled: !triggers.includes('click'),
113
+ openedByRef: openedByRef
114
+ });
115
+ var _useHoverTriggerRef = useHoverTriggerRef({
116
+ onOpen: preloadRef.loadAndOpen,
117
+ onClose: handleClose,
118
+ isOpen: isOpen,
119
+ isDisabled: !triggers.includes('hover'),
120
+ openedByRef: openedByRef
121
+ }),
122
+ hoverTriggerRef = _useHoverTriggerRef.triggerRef,
123
+ hoverContentRef = _useHoverTriggerRef.contentRef;
124
+ useEffect(function () {
125
+ if (!isOpen) {
126
+ openedByRef.current = null;
127
+ }
128
+ }, [isOpen]);
129
+ var popupTrigger = useCallback(function (triggerProps) {
130
+ var mergedRef = mergeRefs([preloadRef, pressableRef, hoverTriggerRef, triggerProps.ref]);
131
+ return trigger(_objectSpread(_objectSpread({}, triggerProps), {}, {
132
+ ref: mergedRef
133
+ }));
134
+ }, [trigger, preloadRef, pressableRef, hoverTriggerRef]);
135
+ var popupContent = useCallback(function (_ref4) {
136
+ var update = _ref4.update;
137
+ updateRef.current = update;
138
+ return /*#__PURE__*/React.createElement("div", {
139
+ ref: hoverContentRef,
140
+ role: "none"
141
+ }, content);
142
+ }, [content, hoverContentRef]);
143
+ return /*#__PURE__*/React.createElement(Popup, _extends({}, popupProps, {
144
+ isOpen: isOpen,
145
+ onClose: handleClose,
146
+ trigger: popupTrigger,
147
+ content: popupContent
148
+ }));
149
+ }
@@ -0,0 +1 @@
1
+ export { PopupTriggerWithHover } from './PopupTriggerWithHover';