@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.
- package/CHANGELOG.md +10 -0
- package/README.md +17 -0
- package/__tests__/unit/PopupTriggerWithHover.test.tsx +141 -0
- package/__tests__/unit/useHoverDelay.test.tsx +99 -0
- package/__tests__/unit/useHoverTriggerRef.test.tsx +121 -0
- package/__tests__/unit/usePreloadRef.test.tsx +118 -0
- package/__tests__/unit/usePressableTriggerRef.test.tsx +104 -0
- package/__tests__/unit/utils.test.tsx +86 -0
- package/afm-cc/tsconfig.json +40 -0
- package/afm-products/tsconfig.json +40 -0
- package/dist/cjs/PopupTriggerWithHover.js +158 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/useHoverDelay.js +38 -0
- package/dist/cjs/useHoverTriggerRef.js +155 -0
- package/dist/cjs/usePreloadRef.js +119 -0
- package/dist/cjs/usePressableTriggerRef.js +69 -0
- package/dist/cjs/utils.js +48 -0
- package/dist/es2019/PopupTriggerWithHover.js +139 -0
- package/dist/es2019/index.js +1 -0
- package/dist/es2019/useHoverDelay.js +32 -0
- package/dist/es2019/useHoverTriggerRef.js +139 -0
- package/dist/es2019/usePreloadRef.js +115 -0
- package/dist/es2019/usePressableTriggerRef.js +62 -0
- package/dist/es2019/utils.js +40 -0
- package/dist/esm/PopupTriggerWithHover.js +149 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/useHoverDelay.js +34 -0
- package/dist/esm/useHoverTriggerRef.js +149 -0
- package/dist/esm/usePreloadRef.js +113 -0
- package/dist/esm/usePressableTriggerRef.js +63 -0
- package/dist/esm/utils.js +41 -0
- package/dist/types/PopupTriggerWithHover.d.ts +28 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/useHoverDelay.d.ts +8 -0
- package/dist/types/useHoverTriggerRef.d.ts +19 -0
- package/dist/types/usePreloadRef.d.ts +13 -0
- package/dist/types/usePressableTriggerRef.d.ts +9 -0
- package/dist/types/utils.d.ts +16 -0
- package/dist/types-ts4.5/PopupTriggerWithHover.d.ts +28 -0
- package/dist/types-ts4.5/index.d.ts +1 -0
- package/dist/types-ts4.5/useHoverDelay.d.ts +8 -0
- package/dist/types-ts4.5/useHoverTriggerRef.d.ts +19 -0
- package/dist/types-ts4.5/usePreloadRef.d.ts +13 -0
- package/dist/types-ts4.5/usePressableTriggerRef.d.ts +9 -0
- package/dist/types-ts4.5/utils.d.ts +16 -0
- package/package.json +81 -0
- package/popup-trigger-with-hover/package.json +17 -0
- package/src/PopupTriggerWithHover.tsx +240 -0
- package/src/index.ts +5 -0
- package/src/useHoverDelay.ts +42 -0
- package/src/useHoverTriggerRef.ts +177 -0
- package/src/usePreloadRef.ts +152 -0
- package/src/usePressableTriggerRef.ts +89 -0
- package/src/utils.ts +49 -0
- package/tsconfig.app.json +46 -0
- package/tsconfig.dev.json +45 -0
- 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';
|