@atlaskit/popup 1.22.1 → 1.23.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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atlaskit/popup
2
2
 
3
+ ## 1.23.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#128022](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/128022)
8
+ [`1495b8f9c9253`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/1495b8f9c9253) -
9
+ [ux] We are testing new focus behavior in non-dialog popup instances behind a feature flag. With
10
+ that in place, all popup instances that don't have role="dialog" applied will have focus traps
11
+ disabled by default. If this fix is successful, it will be available in a later release.
12
+
13
+ ## 1.22.2
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies
18
+
3
19
  ## 1.22.1
4
20
 
5
21
  ### Patch Changes
@@ -105,13 +105,17 @@ function PopperWrapper(_ref) {
105
105
  initialFocusRef = _useState4[0],
106
106
  setInitialFocusRef = _useState4[1];
107
107
 
108
- // We have cases when we need to prohibit focus locking
109
- // e.g. in DropdownMenu
108
+ // We have cases where we need to close the Popup on Tab press.
109
+ // Example: DropdownMenu
110
110
  var shouldCloseOnTab = shouldRenderToParent && shouldDisableFocusLock;
111
+ var shouldDisableFocusTrap = role !== 'dialog';
111
112
  (0, _useFocusManager.useFocusManager)({
112
113
  initialFocusRef: initialFocusRef,
113
114
  popupRef: popupRef,
114
- shouldCloseOnTab: shouldCloseOnTab
115
+ shouldCloseOnTab: shouldCloseOnTab,
116
+ triggerRef: triggerRef,
117
+ autoFocus: autoFocus,
118
+ shouldDisableFocusTrap: shouldDisableFocusTrap
115
119
  });
116
120
  (0, _useCloseManager.useCloseManager)({
117
121
  isOpen: isOpen,
@@ -119,7 +123,10 @@ function PopperWrapper(_ref) {
119
123
  popupRef: popupRef,
120
124
  triggerRef: triggerRef,
121
125
  shouldUseCaptureOnOutsideClick: shouldUseCaptureOnOutsideClick,
122
- shouldCloseOnTab: shouldCloseOnTab
126
+ shouldCloseOnTab: shouldCloseOnTab,
127
+ autoFocus: autoFocus,
128
+ shouldDisableFocusTrap: shouldDisableFocusTrap,
129
+ shouldRenderToParent: shouldRenderToParent
123
130
  });
124
131
  var _UNSAFE_useLayering = (0, _layering.UNSAFE_useLayering)(),
125
132
  currentLevel = _UNSAFE_useLayering.currentLevel;
package/dist/cjs/popup.js CHANGED
@@ -10,6 +10,7 @@ var _react = require("react");
10
10
  var _react2 = require("@emotion/react");
11
11
  var _reactUid = require("react-uid");
12
12
  var _layering = require("@atlaskit/layering");
13
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
13
14
  var _popper = require("@atlaskit/popper");
14
15
  var _portal = _interopRequireDefault(require("@atlaskit/portal"));
15
16
  var _primitives = require("@atlaskit/primitives");
@@ -102,7 +103,7 @@ var Popup = exports.Popup = /*#__PURE__*/(0, _react.memo)(function (_ref) {
102
103
  ref: getMergedTriggerRef(ref, setTriggerRef, isOpen),
103
104
  'aria-controls': isOpen ? id : undefined,
104
105
  'aria-expanded': isOpen,
105
- 'aria-haspopup': true
106
+ 'aria-haspopup': role === 'dialog' && (0, _platformFeatureFlags.fg)('platform_dst_popup-disable-focuslock') ? 'dialog' : true
106
107
  });
107
108
  }), isOpen && (shouldRenderToParent || shouldFitContainer ? renderPopperWrapper : (0, _react2.jsx)(_portal.default, {
108
109
  zIndex: zIndex
@@ -9,6 +9,9 @@ var _react = require("react");
9
9
  var _bindEventListener = require("bind-event-listener");
10
10
  var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
11
11
  var _layering = require("@atlaskit/layering");
12
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
13
+ var _isElementInteractive = require("./utils/is-element-interactive");
14
+ var _useAnimationFrame2 = require("./utils/use-animation-frame");
12
15
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
13
16
 
14
17
  var useCloseManager = exports.useCloseManager = function useCloseManager(_ref) {
@@ -16,11 +19,17 @@ var useCloseManager = exports.useCloseManager = function useCloseManager(_ref) {
16
19
  onClose = _ref.onClose,
17
20
  popupRef = _ref.popupRef,
18
21
  triggerRef = _ref.triggerRef,
22
+ autoFocus = _ref.autoFocus,
23
+ shouldDisableFocusTrap = _ref.shouldDisableFocusTrap,
19
24
  capture = _ref.shouldUseCaptureOnOutsideClick,
20
- shouldCloseOnTab = _ref.shouldCloseOnTab;
25
+ shouldCloseOnTab = _ref.shouldCloseOnTab,
26
+ shouldRenderToParent = _ref.shouldRenderToParent;
21
27
  var _UNSAFE_useLayering = (0, _layering.UNSAFE_useLayering)(),
22
28
  isLayerDisabled = _UNSAFE_useLayering.isLayerDisabled,
23
29
  currentLevel = _UNSAFE_useLayering.currentLevel;
30
+ var _useAnimationFrame = (0, _useAnimationFrame2.useAnimationFrame)(),
31
+ requestFrame = _useAnimationFrame.requestFrame,
32
+ cancelAllFrames = _useAnimationFrame.cancelAllFrames;
24
33
  (0, _react.useEffect)(function () {
25
34
  if (!isOpen || !popupRef) {
26
35
  return _noop.default;
@@ -29,6 +38,13 @@ var useCloseManager = exports.useCloseManager = function useCloseManager(_ref) {
29
38
  if (onClose) {
30
39
  onClose(event);
31
40
  }
41
+ if (shouldDisableFocusTrap && (0, _platformFeatureFlags.fg)('platform_dst_popup-disable-focuslock')) {
42
+ // Restoring the normal focus order for trigger.
43
+ triggerRef === null || triggerRef === void 0 || triggerRef.setAttribute('tabindex', '0');
44
+ if (popupRef && autoFocus) {
45
+ popupRef.setAttribute('tabindex', '0');
46
+ }
47
+ }
32
48
  };
33
49
 
34
50
  // This check is required for cases where components like
@@ -44,23 +60,98 @@ var useCloseManager = exports.useCloseManager = function useCloseManager(_ref) {
44
60
  if (!doesDomNodeExist) {
45
61
  return;
46
62
  }
47
- if (isLayerDisabled()) {
48
- //if it is a disabled layer, we need to disable its click listener.
49
- return;
63
+ if ((0, _platformFeatureFlags.fg)('platform_dst_popup-disable-focuslock')) {
64
+ var _document$activeEleme;
65
+ if (isLayerDisabled() && (_document$activeEleme = document.activeElement) !== null && _document$activeEleme !== void 0 && _document$activeEleme.closest('[aria-modal]')) {
66
+ //if it is a disabled layer, we need to disable its click listener.
67
+ return;
68
+ }
69
+ } else {
70
+ if (isLayerDisabled()) {
71
+ //if it is a disabled layer, we need to disable its click listener.
72
+ return;
73
+ }
50
74
  }
51
75
  var isClickOnPopup = popupRef && popupRef.contains(target);
52
76
  var isClickOnTrigger = triggerRef && triggerRef.contains(target);
53
77
  if (!isClickOnPopup && !isClickOnTrigger) {
54
78
  closePopup(event);
79
+ // If there was an outside click on a non-interactive element, the focus should be on the trigger.
80
+ if (document.activeElement && !(0, _isElementInteractive.isInteractiveElement)(document.activeElement) && (0, _platformFeatureFlags.fg)('platform_dst_popup-disable-focuslock')) {
81
+ triggerRef === null || triggerRef === void 0 || triggerRef.focus();
82
+ }
55
83
  }
56
84
  };
57
85
  var onKeyDown = function onKeyDown(event) {
58
- if (isLayerDisabled()) {
59
- return;
60
- }
61
- var key = event.key;
62
- if (key === 'Escape' || key === 'Esc' || shouldCloseOnTab && key === 'Tab') {
63
- closePopup(event);
86
+ if ((0, _platformFeatureFlags.fg)('platform_dst_popup-disable-focuslock')) {
87
+ var key = event.key,
88
+ shiftKey = event.shiftKey;
89
+ if (shiftKey && key === 'Tab' && !shouldRenderToParent) {
90
+ if (isLayerDisabled()) {
91
+ return;
92
+ }
93
+ // We need to move the focus to the popup trigger when the popup is displayed in React.Portal.
94
+ requestFrame(function () {
95
+ var isPopupFocusOut = popupRef && !popupRef.contains(document.activeElement);
96
+ if (isPopupFocusOut) {
97
+ closePopup(event);
98
+ if (currentLevel === 1) {
99
+ triggerRef === null || triggerRef === void 0 || triggerRef.focus();
100
+ }
101
+ }
102
+ });
103
+ return;
104
+ }
105
+ if (key === 'Tab') {
106
+ var _document$activeEleme2;
107
+ // We have cases where we need to close the Popup on Tab press.
108
+ // Example: DropdownMenu
109
+ if (shouldCloseOnTab) {
110
+ if (isLayerDisabled()) {
111
+ return;
112
+ }
113
+ closePopup(event);
114
+ return;
115
+ }
116
+ if (isLayerDisabled() && (_document$activeEleme2 = document.activeElement) !== null && _document$activeEleme2 !== void 0 && _document$activeEleme2.closest('[aria-modal]')) {
117
+ return;
118
+ }
119
+ if (shouldDisableFocusTrap) {
120
+ if (shouldRenderToParent) {
121
+ // We need to move the focus to the previous interactive element before popup trigger
122
+ requestFrame(function () {
123
+ var isPopupFocusOut = popupRef && !popupRef.contains(document.activeElement);
124
+ if (isPopupFocusOut) {
125
+ closePopup(event);
126
+ }
127
+ });
128
+ } else {
129
+ requestFrame(function () {
130
+ if (!document.hasFocus()) {
131
+ closePopup(event);
132
+ }
133
+ });
134
+ }
135
+ return;
136
+ }
137
+ }
138
+ if (isLayerDisabled()) {
139
+ return;
140
+ }
141
+ if (key === 'Escape' || key === 'Esc') {
142
+ if (triggerRef && autoFocus) {
143
+ triggerRef.focus();
144
+ }
145
+ closePopup(event);
146
+ }
147
+ } else {
148
+ if (isLayerDisabled()) {
149
+ return;
150
+ }
151
+ var _key = event.key;
152
+ if (_key === 'Escape' || _key === 'Esc' || shouldCloseOnTab && _key === 'Tab') {
153
+ closePopup(event);
154
+ }
64
155
  }
65
156
  };
66
157
  var unbind = (0, _bindEventListener.bindAll)(window, [{
@@ -82,15 +173,13 @@ var useCloseManager = exports.useCloseManager = function useCloseManager(_ref) {
82
173
  if (isLayerDisabled() || !(document.activeElement instanceof HTMLIFrameElement)) {
83
174
  return;
84
175
  }
85
- var wrapper = document.activeElement.closest('[data-ds--level]');
86
- if (!wrapper || currentLevel > Number(wrapper.getAttribute('data-ds--level'))) {
87
- closePopup(e);
88
- }
176
+ closePopup(e);
89
177
  }
90
178
  });
91
179
  return function () {
180
+ cancelAllFrames();
92
181
  unbind();
93
182
  unbindBlur();
94
183
  };
95
- }, [isOpen, onClose, popupRef, triggerRef, capture, isLayerDisabled, shouldCloseOnTab, currentLevel]);
184
+ }, [isOpen, onClose, popupRef, triggerRef, autoFocus, shouldDisableFocusTrap, capture, isLayerDisabled, shouldCloseOnTab, currentLevel, shouldRenderToParent, requestFrame, cancelAllFrames]);
96
185
  };
@@ -8,14 +8,34 @@ exports.useFocusManager = void 0;
8
8
  var _react = require("react");
9
9
  var _focusTrap = _interopRequireDefault(require("focus-trap"));
10
10
  var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
11
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
12
+ var _useAnimationFrame2 = require("./utils/use-animation-frame");
11
13
  var useFocusManager = exports.useFocusManager = function useFocusManager(_ref) {
12
14
  var initialFocusRef = _ref.initialFocusRef,
13
15
  popupRef = _ref.popupRef,
14
- shouldCloseOnTab = _ref.shouldCloseOnTab;
16
+ triggerRef = _ref.triggerRef,
17
+ autoFocus = _ref.autoFocus,
18
+ shouldCloseOnTab = _ref.shouldCloseOnTab,
19
+ shouldDisableFocusTrap = _ref.shouldDisableFocusTrap;
20
+ var _useAnimationFrame = (0, _useAnimationFrame2.useAnimationFrame)(),
21
+ requestFrame = _useAnimationFrame.requestFrame,
22
+ cancelAllFrames = _useAnimationFrame.cancelAllFrames;
15
23
  (0, _react.useEffect)(function () {
16
24
  if (!popupRef || shouldCloseOnTab) {
17
25
  return _noop.default;
18
26
  }
27
+ if (shouldDisableFocusTrap && (0, _platformFeatureFlags.fg)('platform_dst_popup-disable-focuslock')) {
28
+ // Plucking trigger & popup content container from the tab order so that
29
+ // when we Shift+Tab, the focus moves to the element before trigger
30
+ requestFrame(function () {
31
+ triggerRef === null || triggerRef === void 0 || triggerRef.setAttribute('tabindex', '-1');
32
+ if (popupRef && autoFocus) {
33
+ popupRef.setAttribute('tabindex', '-1');
34
+ }
35
+ (initialFocusRef || popupRef).focus();
36
+ });
37
+ return _noop.default;
38
+ }
19
39
  var trapConfig = {
20
40
  clickOutsideDeactivates: true,
21
41
  escapeDeactivates: true,
@@ -24,19 +44,14 @@ var useFocusManager = exports.useFocusManager = function useFocusManager(_ref) {
24
44
  returnFocusOnDeactivate: true
25
45
  };
26
46
  var focusTrap = (0, _focusTrap.default)(popupRef, trapConfig);
27
- var frameId = null;
28
47
 
29
- // wait for the popup to reposition itself before we focus
30
- frameId = requestAnimationFrame(function () {
31
- frameId = null;
48
+ // Wait for the popup to reposition itself before we focus
49
+ requestFrame(function () {
32
50
  focusTrap.activate();
33
51
  });
34
52
  return function () {
35
- if (frameId != null) {
36
- cancelAnimationFrame(frameId);
37
- frameId = null;
38
- }
53
+ cancelAllFrames();
39
54
  focusTrap.deactivate();
40
55
  };
41
- }, [popupRef, initialFocusRef, shouldCloseOnTab]);
56
+ }, [popupRef, triggerRef, autoFocus, initialFocusRef, shouldCloseOnTab, shouldDisableFocusTrap, requestFrame, cancelAllFrames]);
42
57
  };
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isInteractiveElement = void 0;
7
+ var interactiveTags = ['button', 'a', 'input', 'select', 'textarea'];
8
+ var isInteractiveElement = exports.isInteractiveElement = function isInteractiveElement(element) {
9
+ if (interactiveTags.includes(element.tagName.toLowerCase())) {
10
+ return true;
11
+ }
12
+ if (element.getAttribute('tabindex') !== null || element.hasAttribute('contenteditable')) {
13
+ return true;
14
+ }
15
+ return false;
16
+ };
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useAnimationFrame = void 0;
7
+ var _react = require("react");
8
+ var useAnimationFrame = exports.useAnimationFrame = function useAnimationFrame() {
9
+ var animationsRef = (0, _react.useRef)([]);
10
+ var requestFrame = (0, _react.useCallback)(function (callback) {
11
+ var id = requestAnimationFrame(callback);
12
+ animationsRef.current.push(id);
13
+ return id;
14
+ }, []);
15
+ var cancelFrame = (0, _react.useCallback)(function (id) {
16
+ cancelAnimationFrame(id);
17
+ animationsRef.current = animationsRef.current.filter(function (frameId) {
18
+ return frameId !== id;
19
+ });
20
+ }, []);
21
+ var cancelAllFrames = (0, _react.useCallback)(function () {
22
+ animationsRef.current.forEach(function (id) {
23
+ return cancelAnimationFrame(id);
24
+ });
25
+ animationsRef.current = [];
26
+ }, []);
27
+ return {
28
+ requestFrame: requestFrame,
29
+ cancelFrame: cancelFrame,
30
+ cancelAllFrames: cancelAllFrames
31
+ };
32
+ };
@@ -90,13 +90,17 @@ function PopperWrapper({
90
90
  const [popupRef, setPopupRef] = useState(null);
91
91
  const [initialFocusRef, setInitialFocusRef] = useState(null);
92
92
 
93
- // We have cases when we need to prohibit focus locking
94
- // e.g. in DropdownMenu
93
+ // We have cases where we need to close the Popup on Tab press.
94
+ // Example: DropdownMenu
95
95
  const shouldCloseOnTab = shouldRenderToParent && shouldDisableFocusLock;
96
+ const shouldDisableFocusTrap = role !== 'dialog';
96
97
  useFocusManager({
97
98
  initialFocusRef,
98
99
  popupRef,
99
- shouldCloseOnTab
100
+ shouldCloseOnTab,
101
+ triggerRef,
102
+ autoFocus,
103
+ shouldDisableFocusTrap
100
104
  });
101
105
  useCloseManager({
102
106
  isOpen,
@@ -104,7 +108,10 @@ function PopperWrapper({
104
108
  popupRef,
105
109
  triggerRef,
106
110
  shouldUseCaptureOnOutsideClick,
107
- shouldCloseOnTab
111
+ shouldCloseOnTab,
112
+ autoFocus,
113
+ shouldDisableFocusTrap,
114
+ shouldRenderToParent
108
115
  });
109
116
  const {
110
117
  currentLevel
@@ -9,6 +9,7 @@ import { memo, useState } from 'react';
9
9
  import { jsx } from '@emotion/react';
10
10
  import { useUID } from 'react-uid';
11
11
  import { UNSAFE_LAYERING } from '@atlaskit/layering';
12
+ import { fg } from '@atlaskit/platform-feature-flags';
12
13
  import { Manager, Reference } from '@atlaskit/popper';
13
14
  import Portal from '@atlaskit/portal';
14
15
  import { Box, xcss } from '@atlaskit/primitives';
@@ -83,7 +84,7 @@ export const Popup = /*#__PURE__*/memo(({
83
84
  ref: getMergedTriggerRef(ref, setTriggerRef, isOpen),
84
85
  'aria-controls': isOpen ? id : undefined,
85
86
  'aria-expanded': isOpen,
86
- 'aria-haspopup': true
87
+ 'aria-haspopup': role === 'dialog' && fg('platform_dst_popup-disable-focuslock') ? 'dialog' : true
87
88
  });
88
89
  }), isOpen && (shouldRenderToParent || shouldFitContainer ? renderPopperWrapper : jsx(Portal, {
89
90
  zIndex: zIndex
@@ -3,18 +3,28 @@ import { useEffect } from 'react';
3
3
  import { bind, bindAll } from 'bind-event-listener';
4
4
  import noop from '@atlaskit/ds-lib/noop';
5
5
  import { UNSAFE_useLayering } from '@atlaskit/layering';
6
+ import { fg } from '@atlaskit/platform-feature-flags';
7
+ import { isInteractiveElement } from './utils/is-element-interactive';
8
+ import { useAnimationFrame } from './utils/use-animation-frame';
6
9
  export const useCloseManager = ({
7
10
  isOpen,
8
11
  onClose,
9
12
  popupRef,
10
13
  triggerRef,
14
+ autoFocus,
15
+ shouldDisableFocusTrap,
11
16
  shouldUseCaptureOnOutsideClick: capture,
12
- shouldCloseOnTab
17
+ shouldCloseOnTab,
18
+ shouldRenderToParent
13
19
  }) => {
14
20
  const {
15
21
  isLayerDisabled,
16
22
  currentLevel
17
23
  } = UNSAFE_useLayering();
24
+ const {
25
+ requestFrame,
26
+ cancelAllFrames
27
+ } = useAnimationFrame();
18
28
  useEffect(() => {
19
29
  if (!isOpen || !popupRef) {
20
30
  return noop;
@@ -23,6 +33,13 @@ export const useCloseManager = ({
23
33
  if (onClose) {
24
34
  onClose(event);
25
35
  }
36
+ if (shouldDisableFocusTrap && fg('platform_dst_popup-disable-focuslock')) {
37
+ // Restoring the normal focus order for trigger.
38
+ triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.setAttribute('tabindex', '0');
39
+ if (popupRef && autoFocus) {
40
+ popupRef.setAttribute('tabindex', '0');
41
+ }
42
+ }
26
43
  };
27
44
 
28
45
  // This check is required for cases where components like
@@ -40,25 +57,102 @@ export const useCloseManager = ({
40
57
  if (!doesDomNodeExist) {
41
58
  return;
42
59
  }
43
- if (isLayerDisabled()) {
44
- //if it is a disabled layer, we need to disable its click listener.
45
- return;
60
+ if (fg('platform_dst_popup-disable-focuslock')) {
61
+ var _document$activeEleme;
62
+ if (isLayerDisabled() && (_document$activeEleme = document.activeElement) !== null && _document$activeEleme !== void 0 && _document$activeEleme.closest('[aria-modal]')) {
63
+ //if it is a disabled layer, we need to disable its click listener.
64
+ return;
65
+ }
66
+ } else {
67
+ if (isLayerDisabled()) {
68
+ //if it is a disabled layer, we need to disable its click listener.
69
+ return;
70
+ }
46
71
  }
47
72
  const isClickOnPopup = popupRef && popupRef.contains(target);
48
73
  const isClickOnTrigger = triggerRef && triggerRef.contains(target);
49
74
  if (!isClickOnPopup && !isClickOnTrigger) {
50
75
  closePopup(event);
76
+ // If there was an outside click on a non-interactive element, the focus should be on the trigger.
77
+ if (document.activeElement && !isInteractiveElement(document.activeElement) && fg('platform_dst_popup-disable-focuslock')) {
78
+ triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.focus();
79
+ }
51
80
  }
52
81
  };
53
82
  const onKeyDown = event => {
54
- if (isLayerDisabled()) {
55
- return;
56
- }
57
- const {
58
- key
59
- } = event;
60
- if (key === 'Escape' || key === 'Esc' || shouldCloseOnTab && key === 'Tab') {
61
- closePopup(event);
83
+ if (fg('platform_dst_popup-disable-focuslock')) {
84
+ const {
85
+ key,
86
+ shiftKey
87
+ } = event;
88
+ if (shiftKey && key === 'Tab' && !shouldRenderToParent) {
89
+ if (isLayerDisabled()) {
90
+ return;
91
+ }
92
+ // We need to move the focus to the popup trigger when the popup is displayed in React.Portal.
93
+ requestFrame(() => {
94
+ const isPopupFocusOut = popupRef && !popupRef.contains(document.activeElement);
95
+ if (isPopupFocusOut) {
96
+ closePopup(event);
97
+ if (currentLevel === 1) {
98
+ triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.focus();
99
+ }
100
+ }
101
+ });
102
+ return;
103
+ }
104
+ if (key === 'Tab') {
105
+ var _document$activeEleme2;
106
+ // We have cases where we need to close the Popup on Tab press.
107
+ // Example: DropdownMenu
108
+ if (shouldCloseOnTab) {
109
+ if (isLayerDisabled()) {
110
+ return;
111
+ }
112
+ closePopup(event);
113
+ return;
114
+ }
115
+ if (isLayerDisabled() && (_document$activeEleme2 = document.activeElement) !== null && _document$activeEleme2 !== void 0 && _document$activeEleme2.closest('[aria-modal]')) {
116
+ return;
117
+ }
118
+ if (shouldDisableFocusTrap) {
119
+ if (shouldRenderToParent) {
120
+ // We need to move the focus to the previous interactive element before popup trigger
121
+ requestFrame(() => {
122
+ const isPopupFocusOut = popupRef && !popupRef.contains(document.activeElement);
123
+ if (isPopupFocusOut) {
124
+ closePopup(event);
125
+ }
126
+ });
127
+ } else {
128
+ requestFrame(() => {
129
+ if (!document.hasFocus()) {
130
+ closePopup(event);
131
+ }
132
+ });
133
+ }
134
+ return;
135
+ }
136
+ }
137
+ if (isLayerDisabled()) {
138
+ return;
139
+ }
140
+ if (key === 'Escape' || key === 'Esc') {
141
+ if (triggerRef && autoFocus) {
142
+ triggerRef.focus();
143
+ }
144
+ closePopup(event);
145
+ }
146
+ } else {
147
+ if (isLayerDisabled()) {
148
+ return;
149
+ }
150
+ const {
151
+ key
152
+ } = event;
153
+ if (key === 'Escape' || key === 'Esc' || shouldCloseOnTab && key === 'Tab') {
154
+ closePopup(event);
155
+ }
62
156
  }
63
157
  };
64
158
  const unbind = bindAll(window, [{
@@ -80,15 +174,13 @@ export const useCloseManager = ({
80
174
  if (isLayerDisabled() || !(document.activeElement instanceof HTMLIFrameElement)) {
81
175
  return;
82
176
  }
83
- const wrapper = document.activeElement.closest('[data-ds--level]');
84
- if (!wrapper || currentLevel > Number(wrapper.getAttribute('data-ds--level'))) {
85
- closePopup(e);
86
- }
177
+ closePopup(e);
87
178
  }
88
179
  });
89
180
  return () => {
181
+ cancelAllFrames();
90
182
  unbind();
91
183
  unbindBlur();
92
184
  };
93
- }, [isOpen, onClose, popupRef, triggerRef, capture, isLayerDisabled, shouldCloseOnTab, currentLevel]);
185
+ }, [isOpen, onClose, popupRef, triggerRef, autoFocus, shouldDisableFocusTrap, capture, isLayerDisabled, shouldCloseOnTab, currentLevel, shouldRenderToParent, requestFrame, cancelAllFrames]);
94
186
  };
@@ -1,15 +1,36 @@
1
1
  import { useEffect } from 'react';
2
2
  import createFocusTrap from 'focus-trap';
3
3
  import noop from '@atlaskit/ds-lib/noop';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { useAnimationFrame } from './utils/use-animation-frame';
4
6
  export const useFocusManager = ({
5
7
  initialFocusRef,
6
8
  popupRef,
7
- shouldCloseOnTab
9
+ triggerRef,
10
+ autoFocus,
11
+ shouldCloseOnTab,
12
+ shouldDisableFocusTrap
8
13
  }) => {
14
+ const {
15
+ requestFrame,
16
+ cancelAllFrames
17
+ } = useAnimationFrame();
9
18
  useEffect(() => {
10
19
  if (!popupRef || shouldCloseOnTab) {
11
20
  return noop;
12
21
  }
22
+ if (shouldDisableFocusTrap && fg('platform_dst_popup-disable-focuslock')) {
23
+ // Plucking trigger & popup content container from the tab order so that
24
+ // when we Shift+Tab, the focus moves to the element before trigger
25
+ requestFrame(() => {
26
+ triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.setAttribute('tabindex', '-1');
27
+ if (popupRef && autoFocus) {
28
+ popupRef.setAttribute('tabindex', '-1');
29
+ }
30
+ (initialFocusRef || popupRef).focus();
31
+ });
32
+ return noop;
33
+ }
13
34
  const trapConfig = {
14
35
  clickOutsideDeactivates: true,
15
36
  escapeDeactivates: true,
@@ -18,19 +39,14 @@ export const useFocusManager = ({
18
39
  returnFocusOnDeactivate: true
19
40
  };
20
41
  const focusTrap = createFocusTrap(popupRef, trapConfig);
21
- let frameId = null;
22
42
 
23
- // wait for the popup to reposition itself before we focus
24
- frameId = requestAnimationFrame(() => {
25
- frameId = null;
43
+ // Wait for the popup to reposition itself before we focus
44
+ requestFrame(() => {
26
45
  focusTrap.activate();
27
46
  });
28
47
  return () => {
29
- if (frameId != null) {
30
- cancelAnimationFrame(frameId);
31
- frameId = null;
32
- }
48
+ cancelAllFrames();
33
49
  focusTrap.deactivate();
34
50
  };
35
- }, [popupRef, initialFocusRef, shouldCloseOnTab]);
51
+ }, [popupRef, triggerRef, autoFocus, initialFocusRef, shouldCloseOnTab, shouldDisableFocusTrap, requestFrame, cancelAllFrames]);
36
52
  };
@@ -0,0 +1,10 @@
1
+ const interactiveTags = ['button', 'a', 'input', 'select', 'textarea'];
2
+ export const isInteractiveElement = element => {
3
+ if (interactiveTags.includes(element.tagName.toLowerCase())) {
4
+ return true;
5
+ }
6
+ if (element.getAttribute('tabindex') !== null || element.hasAttribute('contenteditable')) {
7
+ return true;
8
+ }
9
+ return false;
10
+ };
@@ -0,0 +1,22 @@
1
+ import { useCallback, useRef } from 'react';
2
+ export const useAnimationFrame = () => {
3
+ const animationsRef = useRef([]);
4
+ const requestFrame = useCallback(callback => {
5
+ const id = requestAnimationFrame(callback);
6
+ animationsRef.current.push(id);
7
+ return id;
8
+ }, []);
9
+ const cancelFrame = useCallback(id => {
10
+ cancelAnimationFrame(id);
11
+ animationsRef.current = animationsRef.current.filter(frameId => frameId !== id);
12
+ }, []);
13
+ const cancelAllFrames = useCallback(() => {
14
+ animationsRef.current.forEach(id => cancelAnimationFrame(id));
15
+ animationsRef.current = [];
16
+ }, []);
17
+ return {
18
+ requestFrame,
19
+ cancelFrame,
20
+ cancelAllFrames
21
+ };
22
+ };
@@ -99,13 +99,17 @@ function PopperWrapper(_ref) {
99
99
  initialFocusRef = _useState4[0],
100
100
  setInitialFocusRef = _useState4[1];
101
101
 
102
- // We have cases when we need to prohibit focus locking
103
- // e.g. in DropdownMenu
102
+ // We have cases where we need to close the Popup on Tab press.
103
+ // Example: DropdownMenu
104
104
  var shouldCloseOnTab = shouldRenderToParent && shouldDisableFocusLock;
105
+ var shouldDisableFocusTrap = role !== 'dialog';
105
106
  useFocusManager({
106
107
  initialFocusRef: initialFocusRef,
107
108
  popupRef: popupRef,
108
- shouldCloseOnTab: shouldCloseOnTab
109
+ shouldCloseOnTab: shouldCloseOnTab,
110
+ triggerRef: triggerRef,
111
+ autoFocus: autoFocus,
112
+ shouldDisableFocusTrap: shouldDisableFocusTrap
109
113
  });
110
114
  useCloseManager({
111
115
  isOpen: isOpen,
@@ -113,7 +117,10 @@ function PopperWrapper(_ref) {
113
117
  popupRef: popupRef,
114
118
  triggerRef: triggerRef,
115
119
  shouldUseCaptureOnOutsideClick: shouldUseCaptureOnOutsideClick,
116
- shouldCloseOnTab: shouldCloseOnTab
120
+ shouldCloseOnTab: shouldCloseOnTab,
121
+ autoFocus: autoFocus,
122
+ shouldDisableFocusTrap: shouldDisableFocusTrap,
123
+ shouldRenderToParent: shouldRenderToParent
117
124
  });
118
125
  var _UNSAFE_useLayering = UNSAFE_useLayering(),
119
126
  currentLevel = _UNSAFE_useLayering.currentLevel;
package/dist/esm/popup.js CHANGED
@@ -10,6 +10,7 @@ import { memo, useState } from 'react';
10
10
  import { jsx } from '@emotion/react';
11
11
  import { useUID } from 'react-uid';
12
12
  import { UNSAFE_LAYERING } from '@atlaskit/layering';
13
+ import { fg } from '@atlaskit/platform-feature-flags';
13
14
  import { Manager, Reference } from '@atlaskit/popper';
14
15
  import Portal from '@atlaskit/portal';
15
16
  import { Box, xcss } from '@atlaskit/primitives';
@@ -94,7 +95,7 @@ export var Popup = /*#__PURE__*/memo(function (_ref) {
94
95
  ref: getMergedTriggerRef(ref, setTriggerRef, isOpen),
95
96
  'aria-controls': isOpen ? id : undefined,
96
97
  'aria-expanded': isOpen,
97
- 'aria-haspopup': true
98
+ 'aria-haspopup': role === 'dialog' && fg('platform_dst_popup-disable-focuslock') ? 'dialog' : true
98
99
  });
99
100
  }), isOpen && (shouldRenderToParent || shouldFitContainer ? renderPopperWrapper : jsx(Portal, {
100
101
  zIndex: zIndex
@@ -3,16 +3,25 @@ import { useEffect } from 'react';
3
3
  import { bind, bindAll } from 'bind-event-listener';
4
4
  import noop from '@atlaskit/ds-lib/noop';
5
5
  import { UNSAFE_useLayering } from '@atlaskit/layering';
6
+ import { fg } from '@atlaskit/platform-feature-flags';
7
+ import { isInteractiveElement } from './utils/is-element-interactive';
8
+ import { useAnimationFrame } from './utils/use-animation-frame';
6
9
  export var useCloseManager = function useCloseManager(_ref) {
7
10
  var isOpen = _ref.isOpen,
8
11
  onClose = _ref.onClose,
9
12
  popupRef = _ref.popupRef,
10
13
  triggerRef = _ref.triggerRef,
14
+ autoFocus = _ref.autoFocus,
15
+ shouldDisableFocusTrap = _ref.shouldDisableFocusTrap,
11
16
  capture = _ref.shouldUseCaptureOnOutsideClick,
12
- shouldCloseOnTab = _ref.shouldCloseOnTab;
17
+ shouldCloseOnTab = _ref.shouldCloseOnTab,
18
+ shouldRenderToParent = _ref.shouldRenderToParent;
13
19
  var _UNSAFE_useLayering = UNSAFE_useLayering(),
14
20
  isLayerDisabled = _UNSAFE_useLayering.isLayerDisabled,
15
21
  currentLevel = _UNSAFE_useLayering.currentLevel;
22
+ var _useAnimationFrame = useAnimationFrame(),
23
+ requestFrame = _useAnimationFrame.requestFrame,
24
+ cancelAllFrames = _useAnimationFrame.cancelAllFrames;
16
25
  useEffect(function () {
17
26
  if (!isOpen || !popupRef) {
18
27
  return noop;
@@ -21,6 +30,13 @@ export var useCloseManager = function useCloseManager(_ref) {
21
30
  if (onClose) {
22
31
  onClose(event);
23
32
  }
33
+ if (shouldDisableFocusTrap && fg('platform_dst_popup-disable-focuslock')) {
34
+ // Restoring the normal focus order for trigger.
35
+ triggerRef === null || triggerRef === void 0 || triggerRef.setAttribute('tabindex', '0');
36
+ if (popupRef && autoFocus) {
37
+ popupRef.setAttribute('tabindex', '0');
38
+ }
39
+ }
24
40
  };
25
41
 
26
42
  // This check is required for cases where components like
@@ -36,23 +52,98 @@ export var useCloseManager = function useCloseManager(_ref) {
36
52
  if (!doesDomNodeExist) {
37
53
  return;
38
54
  }
39
- if (isLayerDisabled()) {
40
- //if it is a disabled layer, we need to disable its click listener.
41
- return;
55
+ if (fg('platform_dst_popup-disable-focuslock')) {
56
+ var _document$activeEleme;
57
+ if (isLayerDisabled() && (_document$activeEleme = document.activeElement) !== null && _document$activeEleme !== void 0 && _document$activeEleme.closest('[aria-modal]')) {
58
+ //if it is a disabled layer, we need to disable its click listener.
59
+ return;
60
+ }
61
+ } else {
62
+ if (isLayerDisabled()) {
63
+ //if it is a disabled layer, we need to disable its click listener.
64
+ return;
65
+ }
42
66
  }
43
67
  var isClickOnPopup = popupRef && popupRef.contains(target);
44
68
  var isClickOnTrigger = triggerRef && triggerRef.contains(target);
45
69
  if (!isClickOnPopup && !isClickOnTrigger) {
46
70
  closePopup(event);
71
+ // If there was an outside click on a non-interactive element, the focus should be on the trigger.
72
+ if (document.activeElement && !isInteractiveElement(document.activeElement) && fg('platform_dst_popup-disable-focuslock')) {
73
+ triggerRef === null || triggerRef === void 0 || triggerRef.focus();
74
+ }
47
75
  }
48
76
  };
49
77
  var onKeyDown = function onKeyDown(event) {
50
- if (isLayerDisabled()) {
51
- return;
52
- }
53
- var key = event.key;
54
- if (key === 'Escape' || key === 'Esc' || shouldCloseOnTab && key === 'Tab') {
55
- closePopup(event);
78
+ if (fg('platform_dst_popup-disable-focuslock')) {
79
+ var key = event.key,
80
+ shiftKey = event.shiftKey;
81
+ if (shiftKey && key === 'Tab' && !shouldRenderToParent) {
82
+ if (isLayerDisabled()) {
83
+ return;
84
+ }
85
+ // We need to move the focus to the popup trigger when the popup is displayed in React.Portal.
86
+ requestFrame(function () {
87
+ var isPopupFocusOut = popupRef && !popupRef.contains(document.activeElement);
88
+ if (isPopupFocusOut) {
89
+ closePopup(event);
90
+ if (currentLevel === 1) {
91
+ triggerRef === null || triggerRef === void 0 || triggerRef.focus();
92
+ }
93
+ }
94
+ });
95
+ return;
96
+ }
97
+ if (key === 'Tab') {
98
+ var _document$activeEleme2;
99
+ // We have cases where we need to close the Popup on Tab press.
100
+ // Example: DropdownMenu
101
+ if (shouldCloseOnTab) {
102
+ if (isLayerDisabled()) {
103
+ return;
104
+ }
105
+ closePopup(event);
106
+ return;
107
+ }
108
+ if (isLayerDisabled() && (_document$activeEleme2 = document.activeElement) !== null && _document$activeEleme2 !== void 0 && _document$activeEleme2.closest('[aria-modal]')) {
109
+ return;
110
+ }
111
+ if (shouldDisableFocusTrap) {
112
+ if (shouldRenderToParent) {
113
+ // We need to move the focus to the previous interactive element before popup trigger
114
+ requestFrame(function () {
115
+ var isPopupFocusOut = popupRef && !popupRef.contains(document.activeElement);
116
+ if (isPopupFocusOut) {
117
+ closePopup(event);
118
+ }
119
+ });
120
+ } else {
121
+ requestFrame(function () {
122
+ if (!document.hasFocus()) {
123
+ closePopup(event);
124
+ }
125
+ });
126
+ }
127
+ return;
128
+ }
129
+ }
130
+ if (isLayerDisabled()) {
131
+ return;
132
+ }
133
+ if (key === 'Escape' || key === 'Esc') {
134
+ if (triggerRef && autoFocus) {
135
+ triggerRef.focus();
136
+ }
137
+ closePopup(event);
138
+ }
139
+ } else {
140
+ if (isLayerDisabled()) {
141
+ return;
142
+ }
143
+ var _key = event.key;
144
+ if (_key === 'Escape' || _key === 'Esc' || shouldCloseOnTab && _key === 'Tab') {
145
+ closePopup(event);
146
+ }
56
147
  }
57
148
  };
58
149
  var unbind = bindAll(window, [{
@@ -74,15 +165,13 @@ export var useCloseManager = function useCloseManager(_ref) {
74
165
  if (isLayerDisabled() || !(document.activeElement instanceof HTMLIFrameElement)) {
75
166
  return;
76
167
  }
77
- var wrapper = document.activeElement.closest('[data-ds--level]');
78
- if (!wrapper || currentLevel > Number(wrapper.getAttribute('data-ds--level'))) {
79
- closePopup(e);
80
- }
168
+ closePopup(e);
81
169
  }
82
170
  });
83
171
  return function () {
172
+ cancelAllFrames();
84
173
  unbind();
85
174
  unbindBlur();
86
175
  };
87
- }, [isOpen, onClose, popupRef, triggerRef, capture, isLayerDisabled, shouldCloseOnTab, currentLevel]);
176
+ }, [isOpen, onClose, popupRef, triggerRef, autoFocus, shouldDisableFocusTrap, capture, isLayerDisabled, shouldCloseOnTab, currentLevel, shouldRenderToParent, requestFrame, cancelAllFrames]);
88
177
  };
@@ -1,14 +1,34 @@
1
1
  import { useEffect } from 'react';
2
2
  import createFocusTrap from 'focus-trap';
3
3
  import noop from '@atlaskit/ds-lib/noop';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { useAnimationFrame } from './utils/use-animation-frame';
4
6
  export var useFocusManager = function useFocusManager(_ref) {
5
7
  var initialFocusRef = _ref.initialFocusRef,
6
8
  popupRef = _ref.popupRef,
7
- shouldCloseOnTab = _ref.shouldCloseOnTab;
9
+ triggerRef = _ref.triggerRef,
10
+ autoFocus = _ref.autoFocus,
11
+ shouldCloseOnTab = _ref.shouldCloseOnTab,
12
+ shouldDisableFocusTrap = _ref.shouldDisableFocusTrap;
13
+ var _useAnimationFrame = useAnimationFrame(),
14
+ requestFrame = _useAnimationFrame.requestFrame,
15
+ cancelAllFrames = _useAnimationFrame.cancelAllFrames;
8
16
  useEffect(function () {
9
17
  if (!popupRef || shouldCloseOnTab) {
10
18
  return noop;
11
19
  }
20
+ if (shouldDisableFocusTrap && fg('platform_dst_popup-disable-focuslock')) {
21
+ // Plucking trigger & popup content container from the tab order so that
22
+ // when we Shift+Tab, the focus moves to the element before trigger
23
+ requestFrame(function () {
24
+ triggerRef === null || triggerRef === void 0 || triggerRef.setAttribute('tabindex', '-1');
25
+ if (popupRef && autoFocus) {
26
+ popupRef.setAttribute('tabindex', '-1');
27
+ }
28
+ (initialFocusRef || popupRef).focus();
29
+ });
30
+ return noop;
31
+ }
12
32
  var trapConfig = {
13
33
  clickOutsideDeactivates: true,
14
34
  escapeDeactivates: true,
@@ -17,19 +37,14 @@ export var useFocusManager = function useFocusManager(_ref) {
17
37
  returnFocusOnDeactivate: true
18
38
  };
19
39
  var focusTrap = createFocusTrap(popupRef, trapConfig);
20
- var frameId = null;
21
40
 
22
- // wait for the popup to reposition itself before we focus
23
- frameId = requestAnimationFrame(function () {
24
- frameId = null;
41
+ // Wait for the popup to reposition itself before we focus
42
+ requestFrame(function () {
25
43
  focusTrap.activate();
26
44
  });
27
45
  return function () {
28
- if (frameId != null) {
29
- cancelAnimationFrame(frameId);
30
- frameId = null;
31
- }
46
+ cancelAllFrames();
32
47
  focusTrap.deactivate();
33
48
  };
34
- }, [popupRef, initialFocusRef, shouldCloseOnTab]);
49
+ }, [popupRef, triggerRef, autoFocus, initialFocusRef, shouldCloseOnTab, shouldDisableFocusTrap, requestFrame, cancelAllFrames]);
35
50
  };
@@ -0,0 +1,10 @@
1
+ var interactiveTags = ['button', 'a', 'input', 'select', 'textarea'];
2
+ export var isInteractiveElement = function isInteractiveElement(element) {
3
+ if (interactiveTags.includes(element.tagName.toLowerCase())) {
4
+ return true;
5
+ }
6
+ if (element.getAttribute('tabindex') !== null || element.hasAttribute('contenteditable')) {
7
+ return true;
8
+ }
9
+ return false;
10
+ };
@@ -0,0 +1,26 @@
1
+ import { useCallback, useRef } from 'react';
2
+ export var useAnimationFrame = function useAnimationFrame() {
3
+ var animationsRef = useRef([]);
4
+ var requestFrame = useCallback(function (callback) {
5
+ var id = requestAnimationFrame(callback);
6
+ animationsRef.current.push(id);
7
+ return id;
8
+ }, []);
9
+ var cancelFrame = useCallback(function (id) {
10
+ cancelAnimationFrame(id);
11
+ animationsRef.current = animationsRef.current.filter(function (frameId) {
12
+ return frameId !== id;
13
+ });
14
+ }, []);
15
+ var cancelAllFrames = useCallback(function () {
16
+ animationsRef.current.forEach(function (id) {
17
+ return cancelAnimationFrame(id);
18
+ });
19
+ animationsRef.current = [];
20
+ }, []);
21
+ return {
22
+ requestFrame: requestFrame,
23
+ cancelFrame: cancelFrame,
24
+ cancelAllFrames: cancelAllFrames
25
+ };
26
+ };
@@ -4,7 +4,7 @@ export interface TriggerProps {
4
4
  ref: Ref<any>;
5
5
  'aria-controls'?: string;
6
6
  'aria-expanded': boolean;
7
- 'aria-haspopup': boolean;
7
+ 'aria-haspopup': boolean | 'dialog';
8
8
  }
9
9
  export type PopupRef = HTMLDivElement | null;
10
10
  export type TriggerRef = HTMLElement | HTMLButtonElement | null;
@@ -164,7 +164,7 @@ interface BaseProps {
164
164
  */
165
165
  shouldFitContainer?: boolean;
166
166
  /**
167
- * This allows the popup disable focus lock. It will only work when `shouldRenderToParent` is `true`.
167
+ * This makes the popup close on Tab key press. It will only work when `shouldRenderToParent` is `true`.
168
168
  * The default is `false`.
169
169
  */
170
170
  shouldDisableFocusLock?: boolean;
@@ -226,11 +226,17 @@ export type CloseManagerHook = Pick<PopupProps, 'isOpen' | 'onClose'> & {
226
226
  triggerRef: TriggerRef;
227
227
  shouldUseCaptureOnOutsideClick?: boolean;
228
228
  shouldCloseOnTab?: boolean;
229
+ shouldDisableFocusTrap: boolean;
230
+ shouldRenderToParent?: boolean;
231
+ autoFocus: boolean;
229
232
  };
230
233
  export type FocusManagerHook = {
231
234
  initialFocusRef: HTMLElement | null;
232
235
  popupRef: PopupRef;
233
236
  shouldCloseOnTab?: boolean;
237
+ triggerRef: TriggerRef;
238
+ autoFocus: boolean;
239
+ shouldDisableFocusTrap: boolean;
234
240
  };
235
241
  export type RepositionOnUpdateProps = PropsWithChildren<{
236
242
  update: PopperChildrenProps['update'];
@@ -1,2 +1,2 @@
1
1
  import { type CloseManagerHook } from './types';
2
- export declare const useCloseManager: ({ isOpen, onClose, popupRef, triggerRef, shouldUseCaptureOnOutsideClick: capture, shouldCloseOnTab, }: CloseManagerHook) => void;
2
+ export declare const useCloseManager: ({ isOpen, onClose, popupRef, triggerRef, autoFocus, shouldDisableFocusTrap, shouldUseCaptureOnOutsideClick: capture, shouldCloseOnTab, shouldRenderToParent, }: CloseManagerHook) => void;
@@ -1,2 +1,2 @@
1
1
  import { type FocusManagerHook } from './types';
2
- export declare const useFocusManager: ({ initialFocusRef, popupRef, shouldCloseOnTab, }: FocusManagerHook) => void;
2
+ export declare const useFocusManager: ({ initialFocusRef, popupRef, triggerRef, autoFocus, shouldCloseOnTab, shouldDisableFocusTrap, }: FocusManagerHook) => void;
@@ -0,0 +1 @@
1
+ export declare const isInteractiveElement: (element: HTMLElement) => boolean;
@@ -0,0 +1,5 @@
1
+ export declare const useAnimationFrame: () => {
2
+ requestFrame: (callback: () => void) => number;
3
+ cancelFrame: (id: number) => void;
4
+ cancelAllFrames: () => void;
5
+ };
@@ -4,7 +4,7 @@ export interface TriggerProps {
4
4
  ref: Ref<any>;
5
5
  'aria-controls'?: string;
6
6
  'aria-expanded': boolean;
7
- 'aria-haspopup': boolean;
7
+ 'aria-haspopup': boolean | 'dialog';
8
8
  }
9
9
  export type PopupRef = HTMLDivElement | null;
10
10
  export type TriggerRef = HTMLElement | HTMLButtonElement | null;
@@ -167,7 +167,7 @@ interface BaseProps {
167
167
  */
168
168
  shouldFitContainer?: boolean;
169
169
  /**
170
- * This allows the popup disable focus lock. It will only work when `shouldRenderToParent` is `true`.
170
+ * This makes the popup close on Tab key press. It will only work when `shouldRenderToParent` is `true`.
171
171
  * The default is `false`.
172
172
  */
173
173
  shouldDisableFocusLock?: boolean;
@@ -229,11 +229,17 @@ export type CloseManagerHook = Pick<PopupProps, 'isOpen' | 'onClose'> & {
229
229
  triggerRef: TriggerRef;
230
230
  shouldUseCaptureOnOutsideClick?: boolean;
231
231
  shouldCloseOnTab?: boolean;
232
+ shouldDisableFocusTrap: boolean;
233
+ shouldRenderToParent?: boolean;
234
+ autoFocus: boolean;
232
235
  };
233
236
  export type FocusManagerHook = {
234
237
  initialFocusRef: HTMLElement | null;
235
238
  popupRef: PopupRef;
236
239
  shouldCloseOnTab?: boolean;
240
+ triggerRef: TriggerRef;
241
+ autoFocus: boolean;
242
+ shouldDisableFocusTrap: boolean;
237
243
  };
238
244
  export type RepositionOnUpdateProps = PropsWithChildren<{
239
245
  update: PopperChildrenProps['update'];
@@ -1,2 +1,2 @@
1
1
  import { type CloseManagerHook } from './types';
2
- export declare const useCloseManager: ({ isOpen, onClose, popupRef, triggerRef, shouldUseCaptureOnOutsideClick: capture, shouldCloseOnTab, }: CloseManagerHook) => void;
2
+ export declare const useCloseManager: ({ isOpen, onClose, popupRef, triggerRef, autoFocus, shouldDisableFocusTrap, shouldUseCaptureOnOutsideClick: capture, shouldCloseOnTab, shouldRenderToParent, }: CloseManagerHook) => void;
@@ -1,2 +1,2 @@
1
1
  import { type FocusManagerHook } from './types';
2
- export declare const useFocusManager: ({ initialFocusRef, popupRef, shouldCloseOnTab, }: FocusManagerHook) => void;
2
+ export declare const useFocusManager: ({ initialFocusRef, popupRef, triggerRef, autoFocus, shouldCloseOnTab, shouldDisableFocusTrap, }: FocusManagerHook) => void;
@@ -0,0 +1 @@
1
+ export declare const isInteractiveElement: (element: HTMLElement) => boolean;
@@ -0,0 +1,5 @@
1
+ export declare const useAnimationFrame: () => {
2
+ requestFrame: (callback: () => void) => number;
3
+ cancelFrame: (id: number) => void;
4
+ cancelAllFrames: () => void;
5
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/popup",
3
- "version": "1.22.1",
3
+ "version": "1.23.0",
4
4
  "description": "A popup displays brief content in an overlay.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -46,7 +46,7 @@
46
46
  "@atlaskit/popper": "^6.2.0",
47
47
  "@atlaskit/portal": "^4.9.0",
48
48
  "@atlaskit/primitives": "^12.0.0",
49
- "@atlaskit/theme": "^12.12.0",
49
+ "@atlaskit/theme": "^13.0.0",
50
50
  "@atlaskit/tokens": "^1.58.0",
51
51
  "@babel/runtime": "^7.0.0",
52
52
  "@emotion/react": "^11.7.1",
@@ -62,9 +62,10 @@
62
62
  },
63
63
  "devDependencies": {
64
64
  "@af/accessibility-testing": "*",
65
+ "@af/integration-testing": "*",
65
66
  "@af/visual-regression": "*",
66
- "@atlaskit/button": "^19.2.0",
67
- "@atlaskit/icon": "^22.12.0",
67
+ "@atlaskit/button": "^20.0.0",
68
+ "@atlaskit/icon": "^22.13.0",
68
69
  "@atlaskit/ssr": "*",
69
70
  "@atlaskit/textfield": "^6.5.0",
70
71
  "@atlaskit/toggle": "^13.3.0",
@@ -109,6 +110,9 @@
109
110
  "platform-feature-flags": {
110
111
  "platform.design-system-team.iframe_gojiv": {
111
112
  "type": "boolean"
113
+ },
114
+ "platform_dst_popup-disable-focuslock": {
115
+ "type": "boolean"
112
116
  }
113
117
  },
114
118
  "homepage": "https://atlassian.design/components/popup/"