@atlaskit/dropdown-menu 12.5.3 → 12.6.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,15 @@
1
1
  # @atlaskit/dropdown-menu
2
2
 
3
+ ## 12.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#70573](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/70573) [`ee7c6dd6b8b2`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/ee7c6dd6b8b2) - [ux] Accessibility changes. We are testing the ability to close the dropdown menu by pressing the Tab key when `shouldRenderToParent` is `true` behind a feature flag. If this fix is successful it will be available in a later release.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+
3
13
  ## 12.5.3
4
14
 
5
15
  ### Patch Changes
@@ -5,7 +5,7 @@ var _typeof = require("@babel/runtime/helpers/typeof");
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
- exports.default = exports.KEY_SPACE = exports.KEY_ENTER = void 0;
8
+ exports.default = void 0;
9
9
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
10
10
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
11
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
@@ -19,6 +19,7 @@ var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
19
19
  var _useControlled = _interopRequireDefault(require("@atlaskit/ds-lib/use-controlled"));
20
20
  var _useFocusEvent = _interopRequireDefault(require("@atlaskit/ds-lib/use-focus-event"));
21
21
  var _chevronDown = _interopRequireDefault(require("@atlaskit/icon/glyph/chevron-down"));
22
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
22
23
  var _popup = _interopRequireDefault(require("@atlaskit/popup"));
23
24
  var _constants = require("@atlaskit/theme/constants");
24
25
  var _focusManager = _interopRequireDefault(require("./internal/components/focus-manager"));
@@ -26,7 +27,9 @@ var _menuWrapper = _interopRequireDefault(require("./internal/components/menu-wr
26
27
  var _selectionStore = _interopRequireDefault(require("./internal/context/selection-store"));
27
28
  var _useRegisterItemWithFocusManager = _interopRequireDefault(require("./internal/hooks/use-register-item-with-focus-manager"));
28
29
  var _useGeneratedId = _interopRequireWildcard(require("./internal/utils/use-generated-id"));
29
- var _excluded = ["ref", "aria-controls", "aria-expanded", "aria-haspopup"]; // eslint-disable-next-line @atlaskit/design-system/no-deprecated-imports
30
+ var _excluded = ["ref", "aria-controls", "aria-expanded", "aria-haspopup"];
31
+ /* eslint-disable import/order */
32
+ // eslint-disable-next-line @atlaskit/design-system/no-deprecated-imports
30
33
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
31
34
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
32
35
  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; }
@@ -42,8 +45,6 @@ var opposites = {
42
45
  auto: 'auto',
43
46
  end: 'start'
44
47
  };
45
- var KEY_SPACE = exports.KEY_SPACE = ' ';
46
- var KEY_ENTER = exports.KEY_ENTER = 'Enter';
47
48
  var getFallbackPlacements = function getFallbackPlacements(placement) {
48
49
  var placementPieces = placement.split('-');
49
50
  var mainAxis = placementPieces[0];
@@ -136,6 +137,7 @@ var DropdownMenu = function DropdownMenu(_ref) {
136
137
  var _itemRef$current;
137
138
  // The trigger element must be focused to avoid problems with an incorrectly focused element after closing DropdownMenu
138
139
  itemRef === null || itemRef === void 0 || (_itemRef$current = itemRef.current) === null || _itemRef$current === void 0 || _itemRef$current.focus();
140
+ setTriggeredUsingKeyboard(false);
139
141
  }
140
142
  setLocalIsOpen(newValue);
141
143
  onOpenChange({
@@ -144,10 +146,25 @@ var DropdownMenu = function DropdownMenu(_ref) {
144
146
  });
145
147
  }, [itemRef, onOpenChange, isLocalOpen, setLocalIsOpen]);
146
148
  var handleOnClose = (0, _react.useCallback)(function (event) {
147
- if (event.key !== 'Escape' && event.target.closest("[id^=".concat(_useGeneratedId.PREFIX, "] [aria-haspopup]"))) {
148
- // Check if it is within dropdown and it is a trigger button
149
- // if it is a nested dropdown, clicking trigger won't close the dropdown
150
- return;
149
+ if ((0, _platformFeatureFlags.getBooleanFF)('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
150
+ if (event.key !== 'Escape' && event.key !== 'Tab' && event.target.closest("[id^=".concat(_useGeneratedId.PREFIX, "] [aria-haspopup]"))) {
151
+ // Check if it is within dropdown and it is a trigger button
152
+ // if it is a nested dropdown, clicking trigger won't close the dropdown
153
+ // Dropdown can be closed by pressing Escape, Tab or Shift + Tab
154
+ return;
155
+ }
156
+ if (event.key === 'Tab' && event.shiftKey || event.key === 'Escape') {
157
+ requestAnimationFrame(function () {
158
+ var _itemRef$current2;
159
+ (_itemRef$current2 = itemRef.current) === null || _itemRef$current2 === void 0 || _itemRef$current2.focus();
160
+ });
161
+ }
162
+ } else {
163
+ if (event.key !== 'Escape' && event.target.closest("[id^=".concat(_useGeneratedId.PREFIX, "] [aria-haspopup]"))) {
164
+ // Check if it is within dropdown and it is a trigger button
165
+ // if it is a nested dropdown, clicking trigger won't close the dropdown
166
+ return;
167
+ }
151
168
  }
152
169
  var newValue = false;
153
170
  setLocalIsOpen(newValue);
@@ -155,7 +172,7 @@ var DropdownMenu = function DropdownMenu(_ref) {
155
172
  isOpen: newValue,
156
173
  event: event
157
174
  });
158
- }, [onOpenChange, setLocalIsOpen]);
175
+ }, [onOpenChange, setLocalIsOpen, itemRef]);
159
176
  var _useFocus = (0, _useFocusEvent.default)(),
160
177
  isFocused = _useFocus.isFocused,
161
178
  bindFocus = _useFocus.bindFocus;
@@ -176,17 +193,35 @@ var DropdownMenu = function DropdownMenu(_ref) {
176
193
  return (0, _bindEventListener.bind)(window, {
177
194
  type: 'keydown',
178
195
  listener: function openOnKeyDown(e) {
179
- if (e.key === _keycodes.KEY_DOWN) {
180
- // prevent page scroll
181
- e.preventDefault();
182
- handleTriggerClicked(e);
183
- } else if ((e.key === KEY_SPACE || e.key === KEY_ENTER) && e.detail === 0) {
184
- // This allows us to focus on the first element if the dropdown was triggered by a custom trigger with a custom onClick
185
- setTriggeredUsingKeyboard(true);
196
+ if ((0, _platformFeatureFlags.getBooleanFF)('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
197
+ var isNestedTriggerButton;
198
+ if (e.target instanceof HTMLElement) {
199
+ isNestedTriggerButton = e.target.closest("[id^=".concat(_useGeneratedId.PREFIX, "] [aria-haspopup]"));
200
+ }
201
+ if (e.key === _keycodes.KEY_DOWN && !isNestedTriggerButton) {
202
+ // prevent page scroll
203
+ e.preventDefault();
204
+ handleTriggerClicked(e);
205
+ } else if ((e.code === _keycodes.KEY_SPACE || e.key === _keycodes.KEY_ENTER) && e.detail === 0) {
206
+ // This allows us to focus on the first element if the dropdown was triggered by a custom trigger with a custom onClick
207
+ setTriggeredUsingKeyboard(true);
208
+ } else if (e.key === _keycodes.KEY_TAB && isNestedTriggerButton) {
209
+ // This closes dropdown if it is a nested dropdown
210
+ handleOnClose(e);
211
+ }
212
+ } else {
213
+ if (e.key === _keycodes.KEY_DOWN) {
214
+ // prevent page scroll
215
+ e.preventDefault();
216
+ handleTriggerClicked(e);
217
+ } else if ((e.code === _keycodes.KEY_SPACE || e.key === _keycodes.KEY_ENTER) && e.detail === 0) {
218
+ // This allows us to focus on the first element if the dropdown was triggered by a custom trigger with a custom onClick
219
+ setTriggeredUsingKeyboard(true);
220
+ }
186
221
  }
187
222
  }
188
223
  });
189
- }, [isFocused, isLocalOpen, handleTriggerClicked]);
224
+ }, [isFocused, isLocalOpen, handleTriggerClicked, handleOnClose]);
190
225
  return /*#__PURE__*/_react.default.createElement(_selectionStore.default, null, /*#__PURE__*/_react.default.createElement(_popup.default, {
191
226
  id: isLocalOpen ? id : undefined,
192
227
  shouldFlip: shouldFlip,
@@ -198,6 +233,7 @@ var DropdownMenu = function DropdownMenu(_ref) {
198
233
  testId: testId && "".concat(testId, "--content"),
199
234
  shouldUseCaptureOnOutsideClick: true,
200
235
  shouldRenderToParent: shouldRenderToParent,
236
+ shouldDisableFocusLock: (0, _platformFeatureFlags.getBooleanFF)('platform.design-system-team.disable-focus-lock-in-popup_7kb4d') ? true : false,
201
237
  trigger: function trigger(_ref2) {
202
238
  var ref = _ref2.ref,
203
239
  ariaControls = _ref2['aria-controls'],
@@ -231,7 +267,9 @@ var DropdownMenu = function DropdownMenu(_ref) {
231
267
  content: function content(_ref3) {
232
268
  var setInitialFocusRef = _ref3.setInitialFocusRef,
233
269
  update = _ref3.update;
234
- return /*#__PURE__*/_react.default.createElement(_focusManager.default, null, /*#__PURE__*/_react.default.createElement(_menuWrapper.default, {
270
+ return /*#__PURE__*/_react.default.createElement(_focusManager.default, {
271
+ onClose: handleOnClose
272
+ }, /*#__PURE__*/_react.default.createElement(_menuWrapper.default, {
235
273
  spacing: spacing,
236
274
  maxHeight: MAX_HEIGHT,
237
275
  maxWidth: 800,
@@ -240,6 +278,9 @@ var DropdownMenu = function DropdownMenu(_ref) {
240
278
  isLoading: isLoading,
241
279
  statusLabel: statusLabel,
242
280
  setInitialFocusRef: isTriggeredUsingKeyboard || autoFocus ? setInitialFocusRef : undefined,
281
+ shouldRenderToParent: shouldRenderToParent,
282
+ isTriggeredUsingKeyboard: isTriggeredUsingKeyboard,
283
+ autoFocus: autoFocus,
243
284
  testId: testId && "".concat(testId, "--menu-wrapper")
244
285
  }, children));
245
286
  }
@@ -27,10 +27,11 @@ var FocusManagerContext = exports.FocusManagerContext = /*#__PURE__*/(0, _react.
27
27
  });
28
28
 
29
29
  /**
30
- * Focus manager logic
30
+ * Focus manager logic.
31
31
  */
32
32
  var FocusManager = function FocusManager(_ref) {
33
- var children = _ref.children;
33
+ var children = _ref.children,
34
+ onClose = _ref.onClose;
34
35
  var menuItemRefs = (0, _react.useRef)([]);
35
36
  var registerRef = (0, _react.useCallback)(function (ref) {
36
37
  if (ref && !menuItemRefs.current.includes(ref)) {
@@ -43,7 +44,7 @@ var FocusManager = function FocusManager(_ref) {
43
44
  (0, _react.useEffect)(function () {
44
45
  return (0, _bindEventListener.bind)(window, {
45
46
  type: 'keydown',
46
- listener: (0, _handleFocus.default)(menuItemRefs.current, isLayerDisabled)
47
+ listener: (0, _handleFocus.default)(menuItemRefs.current, isLayerDisabled, onClose)
47
48
  });
48
49
  });
49
50
  var contextValue = {
@@ -8,6 +8,7 @@ exports.default = void 0;
8
8
  var _react = require("react");
9
9
  var _react2 = require("@emotion/react");
10
10
  var _menuGroup = _interopRequireDefault(require("@atlaskit/menu/menu-group"));
11
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
11
12
  var _primitives = require("@atlaskit/primitives");
12
13
  var _spinner = _interopRequireDefault(require("@atlaskit/spinner"));
13
14
  var _focusManager = require("../components/focus-manager");
@@ -49,8 +50,11 @@ var MenuWrapper = function MenuWrapper(_ref2) {
49
50
  onUpdate = _ref2.onUpdate,
50
51
  statusLabel = _ref2.statusLabel,
51
52
  setInitialFocusRef = _ref2.setInitialFocusRef,
53
+ shouldRenderToParent = _ref2.shouldRenderToParent,
52
54
  spacing = _ref2.spacing,
53
- testId = _ref2.testId;
55
+ testId = _ref2.testId,
56
+ isTriggeredUsingKeyboard = _ref2.isTriggeredUsingKeyboard,
57
+ autoFocus = _ref2.autoFocus;
54
58
  var _useContext = (0, _react.useContext)(_focusManager.FocusManagerContext),
55
59
  menuItemRefs = _useContext.menuItemRefs;
56
60
  var closeOnMenuItemClick = function closeOnMenuItemClick(e) {
@@ -79,8 +83,13 @@ var MenuWrapper = function MenuWrapper(_ref2) {
79
83
  var firstFocusableRef = (_menuItemRefs$find = menuItemRefs.find(function (ref) {
80
84
  return !ref.hasAttribute('disabled');
81
85
  })) !== null && _menuItemRefs$find !== void 0 ? _menuItemRefs$find : null;
86
+ if ((0, _platformFeatureFlags.getBooleanFF)('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
87
+ if (shouldRenderToParent && (isTriggeredUsingKeyboard || autoFocus)) {
88
+ firstFocusableRef === null || firstFocusableRef === void 0 || firstFocusableRef.focus();
89
+ }
90
+ }
82
91
  setInitialFocusRef === null || setInitialFocusRef === void 0 || setInitialFocusRef(firstFocusableRef);
83
- }, [menuItemRefs, setInitialFocusRef]);
92
+ }, [menuItemRefs, setInitialFocusRef, autoFocus, shouldRenderToParent, isTriggeredUsingKeyboard]);
84
93
  return (0, _react2.jsx)(_menuGroup.default, {
85
94
  isLoading: isLoading,
86
95
  maxHeight: maxHeight,
@@ -7,11 +7,12 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.default = handleFocus;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
9
  var _keycodes = require("@atlaskit/ds-lib/keycodes");
10
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
10
11
  var _actionMap;
11
12
  var actionMap = (_actionMap = {}, (0, _defineProperty2.default)(_actionMap, _keycodes.KEY_DOWN, 'next'), (0, _defineProperty2.default)(_actionMap, _keycodes.KEY_UP, 'prev'), (0, _defineProperty2.default)(_actionMap, _keycodes.KEY_HOME, 'first'), (0, _defineProperty2.default)(_actionMap, _keycodes.KEY_END, 'last'), _actionMap);
12
13
 
13
14
  /**
14
- * currentFocusedIdx + 1 will not work if the next focusable element
15
+ * `currentFocusedIdx + 1` will not work if the next focusable element
15
16
  * is disabled. So, we need to iterate through the following menu items
16
17
  * to find one that isn't disabled. If all following elements are disabled,
17
18
  * return undefined.
@@ -27,7 +28,7 @@ var getNextFocusableElement = function getNextFocusableElement(refs, currentFocu
27
28
  };
28
29
 
29
30
  /**
30
- * currentFocusedIdx - 1 will not work if the prev focusable element
31
+ * `currentFocusedIdx - 1` will not work if the prev focusable element
31
32
  * is disabled. So, we need to iterate through the previous menu items
32
33
  * to find one that isn't disabled. If all previous elements are disabled,
33
34
  * return undefined.
@@ -41,13 +42,20 @@ var getPrevFocusableElement = function getPrevFocusableElement(refs, currentFocu
41
42
  currentFocusedIdx--;
42
43
  }
43
44
  };
44
- function handleFocus(refs, isLayerDisabled) {
45
+ function handleFocus(refs, isLayerDisabled, onClose) {
45
46
  return function (e) {
46
47
  var currentFocusedIdx = refs.findIndex(function (el) {
47
48
  var _document$activeEleme;
48
49
  return (_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 ? void 0 : _document$activeEleme.isSameNode(el);
49
50
  });
50
51
  if (isLayerDisabled()) {
52
+ if ((0, _platformFeatureFlags.getBooleanFF)('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
53
+ // if nested dropdown isOpen we need to close on Tab key press
54
+ if (e.key === _keycodes.KEY_TAB && !e.shiftKey) {
55
+ onClose(e);
56
+ }
57
+ }
58
+
51
59
  // if it is a nested dropdown and the level of the given dropdown is not the current level,
52
60
  // we don't need to have focus on it
53
61
  return;
@@ -60,6 +68,11 @@ function handleFocus(refs, isLayerDisabled) {
60
68
  if (currentFocusedIdx < refs.length - 1) {
61
69
  var _nextFocusableElement = getNextFocusableElement(refs, currentFocusedIdx);
62
70
  _nextFocusableElement === null || _nextFocusableElement === void 0 || _nextFocusableElement.focus();
71
+ } else {
72
+ if ((0, _platformFeatureFlags.getBooleanFF)('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
73
+ var firstFocusableElement = getNextFocusableElement(refs, -1);
74
+ firstFocusableElement === null || firstFocusableElement === void 0 || firstFocusableElement.focus();
75
+ }
63
76
  }
64
77
  break;
65
78
  case 'prev':
@@ -68,6 +81,11 @@ function handleFocus(refs, isLayerDisabled) {
68
81
  if (currentFocusedIdx > 0) {
69
82
  var _prevFocusableElement = getPrevFocusableElement(refs, currentFocusedIdx);
70
83
  _prevFocusableElement === null || _prevFocusableElement === void 0 || _prevFocusableElement.focus();
84
+ } else {
85
+ if ((0, _platformFeatureFlags.getBooleanFF)('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
86
+ var lastFocusableElement = getPrevFocusableElement(refs, refs.length);
87
+ lastFocusableElement === null || lastFocusableElement === void 0 || lastFocusableElement.focus();
88
+ }
71
89
  }
72
90
  break;
73
91
  case 'first':
@@ -1,13 +1,15 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
+ /* eslint-disable import/order */
2
3
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
4
  import { bind } from 'bind-event-listener';
4
5
  import Button from '@atlaskit/button/new';
5
- import { KEY_DOWN } from '@atlaskit/ds-lib/keycodes';
6
+ import { KEY_DOWN, KEY_ENTER, KEY_SPACE, KEY_TAB } from '@atlaskit/ds-lib/keycodes';
6
7
  import mergeRefs from '@atlaskit/ds-lib/merge-refs';
7
8
  import noop from '@atlaskit/ds-lib/noop';
8
9
  import useControlledState from '@atlaskit/ds-lib/use-controlled';
9
10
  import useFocus from '@atlaskit/ds-lib/use-focus-event';
10
11
  import ExpandIcon from '@atlaskit/icon/glyph/chevron-down';
12
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
11
13
  import Popup from '@atlaskit/popup';
12
14
  // eslint-disable-next-line @atlaskit/design-system/no-deprecated-imports
13
15
  import { gridSize as gridSizeFn, layers } from '@atlaskit/theme/constants';
@@ -27,8 +29,6 @@ const opposites = {
27
29
  auto: 'auto',
28
30
  end: 'start'
29
31
  };
30
- export const KEY_SPACE = ' ';
31
- export const KEY_ENTER = 'Enter';
32
32
  const getFallbackPlacements = placement => {
33
33
  const placementPieces = placement.split('-');
34
34
  const mainAxis = placementPieces[0];
@@ -106,6 +106,7 @@ const DropdownMenu = ({
106
106
  var _itemRef$current;
107
107
  // The trigger element must be focused to avoid problems with an incorrectly focused element after closing DropdownMenu
108
108
  itemRef === null || itemRef === void 0 ? void 0 : (_itemRef$current = itemRef.current) === null || _itemRef$current === void 0 ? void 0 : _itemRef$current.focus();
109
+ setTriggeredUsingKeyboard(false);
109
110
  }
110
111
  setLocalIsOpen(newValue);
111
112
  onOpenChange({
@@ -114,10 +115,25 @@ const DropdownMenu = ({
114
115
  });
115
116
  }, [itemRef, onOpenChange, isLocalOpen, setLocalIsOpen]);
116
117
  const handleOnClose = useCallback(event => {
117
- if (event.key !== 'Escape' && event.target.closest(`[id^=${PREFIX}] [aria-haspopup]`)) {
118
- // Check if it is within dropdown and it is a trigger button
119
- // if it is a nested dropdown, clicking trigger won't close the dropdown
120
- return;
118
+ if (getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
119
+ if (event.key !== 'Escape' && event.key !== 'Tab' && event.target.closest(`[id^=${PREFIX}] [aria-haspopup]`)) {
120
+ // Check if it is within dropdown and it is a trigger button
121
+ // if it is a nested dropdown, clicking trigger won't close the dropdown
122
+ // Dropdown can be closed by pressing Escape, Tab or Shift + Tab
123
+ return;
124
+ }
125
+ if (event.key === 'Tab' && event.shiftKey || event.key === 'Escape') {
126
+ requestAnimationFrame(() => {
127
+ var _itemRef$current2;
128
+ (_itemRef$current2 = itemRef.current) === null || _itemRef$current2 === void 0 ? void 0 : _itemRef$current2.focus();
129
+ });
130
+ }
131
+ } else {
132
+ if (event.key !== 'Escape' && event.target.closest(`[id^=${PREFIX}] [aria-haspopup]`)) {
133
+ // Check if it is within dropdown and it is a trigger button
134
+ // if it is a nested dropdown, clicking trigger won't close the dropdown
135
+ return;
136
+ }
121
137
  }
122
138
  const newValue = false;
123
139
  setLocalIsOpen(newValue);
@@ -125,7 +141,7 @@ const DropdownMenu = ({
125
141
  isOpen: newValue,
126
142
  event
127
143
  });
128
- }, [onOpenChange, setLocalIsOpen]);
144
+ }, [onOpenChange, setLocalIsOpen, itemRef]);
129
145
  const {
130
146
  isFocused,
131
147
  bindFocus
@@ -147,17 +163,35 @@ const DropdownMenu = ({
147
163
  return bind(window, {
148
164
  type: 'keydown',
149
165
  listener: function openOnKeyDown(e) {
150
- if (e.key === KEY_DOWN) {
151
- // prevent page scroll
152
- e.preventDefault();
153
- handleTriggerClicked(e);
154
- } else if ((e.key === KEY_SPACE || e.key === KEY_ENTER) && e.detail === 0) {
155
- // This allows us to focus on the first element if the dropdown was triggered by a custom trigger with a custom onClick
156
- setTriggeredUsingKeyboard(true);
166
+ if (getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
167
+ let isNestedTriggerButton;
168
+ if (e.target instanceof HTMLElement) {
169
+ isNestedTriggerButton = e.target.closest(`[id^=${PREFIX}] [aria-haspopup]`);
170
+ }
171
+ if (e.key === KEY_DOWN && !isNestedTriggerButton) {
172
+ // prevent page scroll
173
+ e.preventDefault();
174
+ handleTriggerClicked(e);
175
+ } else if ((e.code === KEY_SPACE || e.key === KEY_ENTER) && e.detail === 0) {
176
+ // This allows us to focus on the first element if the dropdown was triggered by a custom trigger with a custom onClick
177
+ setTriggeredUsingKeyboard(true);
178
+ } else if (e.key === KEY_TAB && isNestedTriggerButton) {
179
+ // This closes dropdown if it is a nested dropdown
180
+ handleOnClose(e);
181
+ }
182
+ } else {
183
+ if (e.key === KEY_DOWN) {
184
+ // prevent page scroll
185
+ e.preventDefault();
186
+ handleTriggerClicked(e);
187
+ } else if ((e.code === KEY_SPACE || e.key === KEY_ENTER) && e.detail === 0) {
188
+ // This allows us to focus on the first element if the dropdown was triggered by a custom trigger with a custom onClick
189
+ setTriggeredUsingKeyboard(true);
190
+ }
157
191
  }
158
192
  }
159
193
  });
160
- }, [isFocused, isLocalOpen, handleTriggerClicked]);
194
+ }, [isFocused, isLocalOpen, handleTriggerClicked, handleOnClose]);
161
195
  return /*#__PURE__*/React.createElement(SelectionStore, null, /*#__PURE__*/React.createElement(Popup, {
162
196
  id: isLocalOpen ? id : undefined,
163
197
  shouldFlip: shouldFlip,
@@ -169,6 +203,7 @@ const DropdownMenu = ({
169
203
  testId: testId && `${testId}--content`,
170
204
  shouldUseCaptureOnOutsideClick: true,
171
205
  shouldRenderToParent: shouldRenderToParent,
206
+ shouldDisableFocusLock: getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d') ? true : false,
172
207
  trigger: ({
173
208
  ref,
174
209
  'aria-controls': ariaControls,
@@ -205,7 +240,9 @@ const DropdownMenu = ({
205
240
  content: ({
206
241
  setInitialFocusRef,
207
242
  update
208
- }) => /*#__PURE__*/React.createElement(FocusManager, null, /*#__PURE__*/React.createElement(MenuWrapper, {
243
+ }) => /*#__PURE__*/React.createElement(FocusManager, {
244
+ onClose: handleOnClose
245
+ }, /*#__PURE__*/React.createElement(MenuWrapper, {
209
246
  spacing: spacing,
210
247
  maxHeight: MAX_HEIGHT,
211
248
  maxWidth: 800,
@@ -214,6 +251,9 @@ const DropdownMenu = ({
214
251
  isLoading: isLoading,
215
252
  statusLabel: statusLabel,
216
253
  setInitialFocusRef: isTriggeredUsingKeyboard || autoFocus ? setInitialFocusRef : undefined,
254
+ shouldRenderToParent: shouldRenderToParent,
255
+ isTriggeredUsingKeyboard: isTriggeredUsingKeyboard,
256
+ autoFocus: autoFocus,
217
257
  testId: testId && `${testId}--menu-wrapper`
218
258
  }, children))
219
259
  }));
@@ -18,10 +18,11 @@ export const FocusManagerContext = /*#__PURE__*/createContext({
18
18
  });
19
19
 
20
20
  /**
21
- * Focus manager logic
21
+ * Focus manager logic.
22
22
  */
23
23
  const FocusManager = ({
24
- children
24
+ children,
25
+ onClose
25
26
  }) => {
26
27
  const menuItemRefs = useRef([]);
27
28
  const registerRef = useCallback(ref => {
@@ -36,7 +37,7 @@ const FocusManager = ({
36
37
  useEffect(() => {
37
38
  return bind(window, {
38
39
  type: 'keydown',
39
- listener: handleFocus(menuItemRefs.current, isLayerDisabled)
40
+ listener: handleFocus(menuItemRefs.current, isLayerDisabled, onClose)
40
41
  });
41
42
  });
42
43
  const contextValue = {
@@ -2,6 +2,7 @@
2
2
  import { useContext, useEffect, useLayoutEffect } from 'react';
3
3
  import { jsx } from '@emotion/react';
4
4
  import MenuGroup from '@atlaskit/menu/menu-group';
5
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
5
6
  import { Box, xcss } from '@atlaskit/primitives';
6
7
  import Spinner from '@atlaskit/spinner';
7
8
  import { FocusManagerContext } from '../components/focus-manager';
@@ -39,8 +40,11 @@ const MenuWrapper = ({
39
40
  onUpdate,
40
41
  statusLabel,
41
42
  setInitialFocusRef,
43
+ shouldRenderToParent,
42
44
  spacing,
43
- testId
45
+ testId,
46
+ isTriggeredUsingKeyboard,
47
+ autoFocus
44
48
  }) => {
45
49
  const {
46
50
  menuItemRefs
@@ -69,8 +73,13 @@ const MenuWrapper = ({
69
73
  useEffect(() => {
70
74
  var _menuItemRefs$find;
71
75
  const firstFocusableRef = (_menuItemRefs$find = menuItemRefs.find(ref => !ref.hasAttribute('disabled'))) !== null && _menuItemRefs$find !== void 0 ? _menuItemRefs$find : null;
76
+ if (getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
77
+ if (shouldRenderToParent && (isTriggeredUsingKeyboard || autoFocus)) {
78
+ firstFocusableRef === null || firstFocusableRef === void 0 ? void 0 : firstFocusableRef.focus();
79
+ }
80
+ }
72
81
  setInitialFocusRef === null || setInitialFocusRef === void 0 ? void 0 : setInitialFocusRef(firstFocusableRef);
73
- }, [menuItemRefs, setInitialFocusRef]);
82
+ }, [menuItemRefs, setInitialFocusRef, autoFocus, shouldRenderToParent, isTriggeredUsingKeyboard]);
74
83
  return jsx(MenuGroup, {
75
84
  isLoading: isLoading,
76
85
  maxHeight: maxHeight,
@@ -1,4 +1,5 @@
1
- import { KEY_DOWN, KEY_END, KEY_HOME, KEY_UP } from '@atlaskit/ds-lib/keycodes';
1
+ import { KEY_DOWN, KEY_END, KEY_HOME, KEY_TAB, KEY_UP } from '@atlaskit/ds-lib/keycodes';
2
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
2
3
  const actionMap = {
3
4
  [KEY_DOWN]: 'next',
4
5
  [KEY_UP]: 'prev',
@@ -7,7 +8,7 @@ const actionMap = {
7
8
  };
8
9
 
9
10
  /**
10
- * currentFocusedIdx + 1 will not work if the next focusable element
11
+ * `currentFocusedIdx + 1` will not work if the next focusable element
11
12
  * is disabled. So, we need to iterate through the following menu items
12
13
  * to find one that isn't disabled. If all following elements are disabled,
13
14
  * return undefined.
@@ -23,7 +24,7 @@ const getNextFocusableElement = (refs, currentFocusedIdx) => {
23
24
  };
24
25
 
25
26
  /**
26
- * currentFocusedIdx - 1 will not work if the prev focusable element
27
+ * `currentFocusedIdx - 1` will not work if the prev focusable element
27
28
  * is disabled. So, we need to iterate through the previous menu items
28
29
  * to find one that isn't disabled. If all previous elements are disabled,
29
30
  * return undefined.
@@ -37,13 +38,20 @@ const getPrevFocusableElement = (refs, currentFocusedIdx) => {
37
38
  currentFocusedIdx--;
38
39
  }
39
40
  };
40
- export default function handleFocus(refs, isLayerDisabled) {
41
+ export default function handleFocus(refs, isLayerDisabled, onClose) {
41
42
  return e => {
42
43
  const currentFocusedIdx = refs.findIndex(el => {
43
44
  var _document$activeEleme;
44
45
  return (_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 ? void 0 : _document$activeEleme.isSameNode(el);
45
46
  });
46
47
  if (isLayerDisabled()) {
48
+ if (getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
49
+ // if nested dropdown isOpen we need to close on Tab key press
50
+ if (e.key === KEY_TAB && !e.shiftKey) {
51
+ onClose(e);
52
+ }
53
+ }
54
+
47
55
  // if it is a nested dropdown and the level of the given dropdown is not the current level,
48
56
  // we don't need to have focus on it
49
57
  return;
@@ -56,6 +64,11 @@ export default function handleFocus(refs, isLayerDisabled) {
56
64
  if (currentFocusedIdx < refs.length - 1) {
57
65
  const nextFocusableElement = getNextFocusableElement(refs, currentFocusedIdx);
58
66
  nextFocusableElement === null || nextFocusableElement === void 0 ? void 0 : nextFocusableElement.focus();
67
+ } else {
68
+ if (getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
69
+ const firstFocusableElement = getNextFocusableElement(refs, -1);
70
+ firstFocusableElement === null || firstFocusableElement === void 0 ? void 0 : firstFocusableElement.focus();
71
+ }
59
72
  }
60
73
  break;
61
74
  case 'prev':
@@ -64,6 +77,11 @@ export default function handleFocus(refs, isLayerDisabled) {
64
77
  if (currentFocusedIdx > 0) {
65
78
  const prevFocusableElement = getPrevFocusableElement(refs, currentFocusedIdx);
66
79
  prevFocusableElement === null || prevFocusableElement === void 0 ? void 0 : prevFocusableElement.focus();
80
+ } else {
81
+ if (getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
82
+ const lastFocusableElement = getPrevFocusableElement(refs, refs.length);
83
+ lastFocusableElement === null || lastFocusableElement === void 0 ? void 0 : lastFocusableElement.focus();
84
+ }
67
85
  }
68
86
  break;
69
87
  case 'first':
@@ -5,15 +5,17 @@ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
5
5
  var _excluded = ["ref", "aria-controls", "aria-expanded", "aria-haspopup"];
6
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
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
+ /* eslint-disable import/order */
8
9
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
9
10
  import { bind } from 'bind-event-listener';
10
11
  import Button from '@atlaskit/button/new';
11
- import { KEY_DOWN } from '@atlaskit/ds-lib/keycodes';
12
+ import { KEY_DOWN, KEY_ENTER, KEY_SPACE, KEY_TAB } from '@atlaskit/ds-lib/keycodes';
12
13
  import mergeRefs from '@atlaskit/ds-lib/merge-refs';
13
14
  import noop from '@atlaskit/ds-lib/noop';
14
15
  import useControlledState from '@atlaskit/ds-lib/use-controlled';
15
16
  import useFocus from '@atlaskit/ds-lib/use-focus-event';
16
17
  import ExpandIcon from '@atlaskit/icon/glyph/chevron-down';
18
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
17
19
  import Popup from '@atlaskit/popup';
18
20
  // eslint-disable-next-line @atlaskit/design-system/no-deprecated-imports
19
21
  import { gridSize as gridSizeFn, layers } from '@atlaskit/theme/constants';
@@ -33,8 +35,6 @@ var opposites = {
33
35
  auto: 'auto',
34
36
  end: 'start'
35
37
  };
36
- export var KEY_SPACE = ' ';
37
- export var KEY_ENTER = 'Enter';
38
38
  var getFallbackPlacements = function getFallbackPlacements(placement) {
39
39
  var placementPieces = placement.split('-');
40
40
  var mainAxis = placementPieces[0];
@@ -127,6 +127,7 @@ var DropdownMenu = function DropdownMenu(_ref) {
127
127
  var _itemRef$current;
128
128
  // The trigger element must be focused to avoid problems with an incorrectly focused element after closing DropdownMenu
129
129
  itemRef === null || itemRef === void 0 || (_itemRef$current = itemRef.current) === null || _itemRef$current === void 0 || _itemRef$current.focus();
130
+ setTriggeredUsingKeyboard(false);
130
131
  }
131
132
  setLocalIsOpen(newValue);
132
133
  onOpenChange({
@@ -135,10 +136,25 @@ var DropdownMenu = function DropdownMenu(_ref) {
135
136
  });
136
137
  }, [itemRef, onOpenChange, isLocalOpen, setLocalIsOpen]);
137
138
  var handleOnClose = useCallback(function (event) {
138
- if (event.key !== 'Escape' && event.target.closest("[id^=".concat(PREFIX, "] [aria-haspopup]"))) {
139
- // Check if it is within dropdown and it is a trigger button
140
- // if it is a nested dropdown, clicking trigger won't close the dropdown
141
- return;
139
+ if (getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
140
+ if (event.key !== 'Escape' && event.key !== 'Tab' && event.target.closest("[id^=".concat(PREFIX, "] [aria-haspopup]"))) {
141
+ // Check if it is within dropdown and it is a trigger button
142
+ // if it is a nested dropdown, clicking trigger won't close the dropdown
143
+ // Dropdown can be closed by pressing Escape, Tab or Shift + Tab
144
+ return;
145
+ }
146
+ if (event.key === 'Tab' && event.shiftKey || event.key === 'Escape') {
147
+ requestAnimationFrame(function () {
148
+ var _itemRef$current2;
149
+ (_itemRef$current2 = itemRef.current) === null || _itemRef$current2 === void 0 || _itemRef$current2.focus();
150
+ });
151
+ }
152
+ } else {
153
+ if (event.key !== 'Escape' && event.target.closest("[id^=".concat(PREFIX, "] [aria-haspopup]"))) {
154
+ // Check if it is within dropdown and it is a trigger button
155
+ // if it is a nested dropdown, clicking trigger won't close the dropdown
156
+ return;
157
+ }
142
158
  }
143
159
  var newValue = false;
144
160
  setLocalIsOpen(newValue);
@@ -146,7 +162,7 @@ var DropdownMenu = function DropdownMenu(_ref) {
146
162
  isOpen: newValue,
147
163
  event: event
148
164
  });
149
- }, [onOpenChange, setLocalIsOpen]);
165
+ }, [onOpenChange, setLocalIsOpen, itemRef]);
150
166
  var _useFocus = useFocus(),
151
167
  isFocused = _useFocus.isFocused,
152
168
  bindFocus = _useFocus.bindFocus;
@@ -167,17 +183,35 @@ var DropdownMenu = function DropdownMenu(_ref) {
167
183
  return bind(window, {
168
184
  type: 'keydown',
169
185
  listener: function openOnKeyDown(e) {
170
- if (e.key === KEY_DOWN) {
171
- // prevent page scroll
172
- e.preventDefault();
173
- handleTriggerClicked(e);
174
- } else if ((e.key === KEY_SPACE || e.key === KEY_ENTER) && e.detail === 0) {
175
- // This allows us to focus on the first element if the dropdown was triggered by a custom trigger with a custom onClick
176
- setTriggeredUsingKeyboard(true);
186
+ if (getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
187
+ var isNestedTriggerButton;
188
+ if (e.target instanceof HTMLElement) {
189
+ isNestedTriggerButton = e.target.closest("[id^=".concat(PREFIX, "] [aria-haspopup]"));
190
+ }
191
+ if (e.key === KEY_DOWN && !isNestedTriggerButton) {
192
+ // prevent page scroll
193
+ e.preventDefault();
194
+ handleTriggerClicked(e);
195
+ } else if ((e.code === KEY_SPACE || e.key === KEY_ENTER) && e.detail === 0) {
196
+ // This allows us to focus on the first element if the dropdown was triggered by a custom trigger with a custom onClick
197
+ setTriggeredUsingKeyboard(true);
198
+ } else if (e.key === KEY_TAB && isNestedTriggerButton) {
199
+ // This closes dropdown if it is a nested dropdown
200
+ handleOnClose(e);
201
+ }
202
+ } else {
203
+ if (e.key === KEY_DOWN) {
204
+ // prevent page scroll
205
+ e.preventDefault();
206
+ handleTriggerClicked(e);
207
+ } else if ((e.code === KEY_SPACE || e.key === KEY_ENTER) && e.detail === 0) {
208
+ // This allows us to focus on the first element if the dropdown was triggered by a custom trigger with a custom onClick
209
+ setTriggeredUsingKeyboard(true);
210
+ }
177
211
  }
178
212
  }
179
213
  });
180
- }, [isFocused, isLocalOpen, handleTriggerClicked]);
214
+ }, [isFocused, isLocalOpen, handleTriggerClicked, handleOnClose]);
181
215
  return /*#__PURE__*/React.createElement(SelectionStore, null, /*#__PURE__*/React.createElement(Popup, {
182
216
  id: isLocalOpen ? id : undefined,
183
217
  shouldFlip: shouldFlip,
@@ -189,6 +223,7 @@ var DropdownMenu = function DropdownMenu(_ref) {
189
223
  testId: testId && "".concat(testId, "--content"),
190
224
  shouldUseCaptureOnOutsideClick: true,
191
225
  shouldRenderToParent: shouldRenderToParent,
226
+ shouldDisableFocusLock: getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d') ? true : false,
192
227
  trigger: function trigger(_ref2) {
193
228
  var ref = _ref2.ref,
194
229
  ariaControls = _ref2['aria-controls'],
@@ -222,7 +257,9 @@ var DropdownMenu = function DropdownMenu(_ref) {
222
257
  content: function content(_ref3) {
223
258
  var setInitialFocusRef = _ref3.setInitialFocusRef,
224
259
  update = _ref3.update;
225
- return /*#__PURE__*/React.createElement(FocusManager, null, /*#__PURE__*/React.createElement(MenuWrapper, {
260
+ return /*#__PURE__*/React.createElement(FocusManager, {
261
+ onClose: handleOnClose
262
+ }, /*#__PURE__*/React.createElement(MenuWrapper, {
226
263
  spacing: spacing,
227
264
  maxHeight: MAX_HEIGHT,
228
265
  maxWidth: 800,
@@ -231,6 +268,9 @@ var DropdownMenu = function DropdownMenu(_ref) {
231
268
  isLoading: isLoading,
232
269
  statusLabel: statusLabel,
233
270
  setInitialFocusRef: isTriggeredUsingKeyboard || autoFocus ? setInitialFocusRef : undefined,
271
+ shouldRenderToParent: shouldRenderToParent,
272
+ isTriggeredUsingKeyboard: isTriggeredUsingKeyboard,
273
+ autoFocus: autoFocus,
234
274
  testId: testId && "".concat(testId, "--menu-wrapper")
235
275
  }, children));
236
276
  }
@@ -18,10 +18,11 @@ export var FocusManagerContext = /*#__PURE__*/createContext({
18
18
  });
19
19
 
20
20
  /**
21
- * Focus manager logic
21
+ * Focus manager logic.
22
22
  */
23
23
  var FocusManager = function FocusManager(_ref) {
24
- var children = _ref.children;
24
+ var children = _ref.children,
25
+ onClose = _ref.onClose;
25
26
  var menuItemRefs = useRef([]);
26
27
  var registerRef = useCallback(function (ref) {
27
28
  if (ref && !menuItemRefs.current.includes(ref)) {
@@ -34,7 +35,7 @@ var FocusManager = function FocusManager(_ref) {
34
35
  useEffect(function () {
35
36
  return bind(window, {
36
37
  type: 'keydown',
37
- listener: handleFocus(menuItemRefs.current, isLayerDisabled)
38
+ listener: handleFocus(menuItemRefs.current, isLayerDisabled, onClose)
38
39
  });
39
40
  });
40
41
  var contextValue = {
@@ -2,6 +2,7 @@
2
2
  import { useContext, useEffect, useLayoutEffect } from 'react';
3
3
  import { jsx } from '@emotion/react';
4
4
  import MenuGroup from '@atlaskit/menu/menu-group';
5
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
5
6
  import { Box, xcss } from '@atlaskit/primitives';
6
7
  import Spinner from '@atlaskit/spinner';
7
8
  import { FocusManagerContext } from '../components/focus-manager';
@@ -41,8 +42,11 @@ var MenuWrapper = function MenuWrapper(_ref2) {
41
42
  onUpdate = _ref2.onUpdate,
42
43
  statusLabel = _ref2.statusLabel,
43
44
  setInitialFocusRef = _ref2.setInitialFocusRef,
45
+ shouldRenderToParent = _ref2.shouldRenderToParent,
44
46
  spacing = _ref2.spacing,
45
- testId = _ref2.testId;
47
+ testId = _ref2.testId,
48
+ isTriggeredUsingKeyboard = _ref2.isTriggeredUsingKeyboard,
49
+ autoFocus = _ref2.autoFocus;
46
50
  var _useContext = useContext(FocusManagerContext),
47
51
  menuItemRefs = _useContext.menuItemRefs;
48
52
  var closeOnMenuItemClick = function closeOnMenuItemClick(e) {
@@ -71,8 +75,13 @@ var MenuWrapper = function MenuWrapper(_ref2) {
71
75
  var firstFocusableRef = (_menuItemRefs$find = menuItemRefs.find(function (ref) {
72
76
  return !ref.hasAttribute('disabled');
73
77
  })) !== null && _menuItemRefs$find !== void 0 ? _menuItemRefs$find : null;
78
+ if (getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
79
+ if (shouldRenderToParent && (isTriggeredUsingKeyboard || autoFocus)) {
80
+ firstFocusableRef === null || firstFocusableRef === void 0 || firstFocusableRef.focus();
81
+ }
82
+ }
74
83
  setInitialFocusRef === null || setInitialFocusRef === void 0 || setInitialFocusRef(firstFocusableRef);
75
- }, [menuItemRefs, setInitialFocusRef]);
84
+ }, [menuItemRefs, setInitialFocusRef, autoFocus, shouldRenderToParent, isTriggeredUsingKeyboard]);
76
85
  return jsx(MenuGroup, {
77
86
  isLoading: isLoading,
78
87
  maxHeight: maxHeight,
@@ -1,10 +1,11 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  var _actionMap;
3
- import { KEY_DOWN, KEY_END, KEY_HOME, KEY_UP } from '@atlaskit/ds-lib/keycodes';
3
+ import { KEY_DOWN, KEY_END, KEY_HOME, KEY_TAB, KEY_UP } from '@atlaskit/ds-lib/keycodes';
4
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
4
5
  var actionMap = (_actionMap = {}, _defineProperty(_actionMap, KEY_DOWN, 'next'), _defineProperty(_actionMap, KEY_UP, 'prev'), _defineProperty(_actionMap, KEY_HOME, 'first'), _defineProperty(_actionMap, KEY_END, 'last'), _actionMap);
5
6
 
6
7
  /**
7
- * currentFocusedIdx + 1 will not work if the next focusable element
8
+ * `currentFocusedIdx + 1` will not work if the next focusable element
8
9
  * is disabled. So, we need to iterate through the following menu items
9
10
  * to find one that isn't disabled. If all following elements are disabled,
10
11
  * return undefined.
@@ -20,7 +21,7 @@ var getNextFocusableElement = function getNextFocusableElement(refs, currentFocu
20
21
  };
21
22
 
22
23
  /**
23
- * currentFocusedIdx - 1 will not work if the prev focusable element
24
+ * `currentFocusedIdx - 1` will not work if the prev focusable element
24
25
  * is disabled. So, we need to iterate through the previous menu items
25
26
  * to find one that isn't disabled. If all previous elements are disabled,
26
27
  * return undefined.
@@ -34,13 +35,20 @@ var getPrevFocusableElement = function getPrevFocusableElement(refs, currentFocu
34
35
  currentFocusedIdx--;
35
36
  }
36
37
  };
37
- export default function handleFocus(refs, isLayerDisabled) {
38
+ export default function handleFocus(refs, isLayerDisabled, onClose) {
38
39
  return function (e) {
39
40
  var currentFocusedIdx = refs.findIndex(function (el) {
40
41
  var _document$activeEleme;
41
42
  return (_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 ? void 0 : _document$activeEleme.isSameNode(el);
42
43
  });
43
44
  if (isLayerDisabled()) {
45
+ if (getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
46
+ // if nested dropdown isOpen we need to close on Tab key press
47
+ if (e.key === KEY_TAB && !e.shiftKey) {
48
+ onClose(e);
49
+ }
50
+ }
51
+
44
52
  // if it is a nested dropdown and the level of the given dropdown is not the current level,
45
53
  // we don't need to have focus on it
46
54
  return;
@@ -53,6 +61,11 @@ export default function handleFocus(refs, isLayerDisabled) {
53
61
  if (currentFocusedIdx < refs.length - 1) {
54
62
  var _nextFocusableElement = getNextFocusableElement(refs, currentFocusedIdx);
55
63
  _nextFocusableElement === null || _nextFocusableElement === void 0 || _nextFocusableElement.focus();
64
+ } else {
65
+ if (getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
66
+ var firstFocusableElement = getNextFocusableElement(refs, -1);
67
+ firstFocusableElement === null || firstFocusableElement === void 0 || firstFocusableElement.focus();
68
+ }
56
69
  }
57
70
  break;
58
71
  case 'prev':
@@ -61,6 +74,11 @@ export default function handleFocus(refs, isLayerDisabled) {
61
74
  if (currentFocusedIdx > 0) {
62
75
  var _prevFocusableElement = getPrevFocusableElement(refs, currentFocusedIdx);
63
76
  _prevFocusableElement === null || _prevFocusableElement === void 0 || _prevFocusableElement.focus();
77
+ } else {
78
+ if (getBooleanFF('platform.design-system-team.disable-focus-lock-in-popup_7kb4d')) {
79
+ var lastFocusableElement = getPrevFocusableElement(refs, refs.length);
80
+ lastFocusableElement === null || lastFocusableElement === void 0 || lastFocusableElement.focus();
81
+ }
64
82
  }
65
83
  break;
66
84
  case 'first':
@@ -1,7 +1,5 @@
1
1
  /// <reference types="react" />
2
2
  import type { DropdownMenuProps } from './types';
3
- export declare const KEY_SPACE = " ";
4
- export declare const KEY_ENTER = "Enter";
5
3
  /**
6
4
  * __Dropdown menu__
7
5
  *
@@ -13,9 +13,10 @@ export declare const FocusManagerContext: React.Context<{
13
13
  registerRef: (ref: FocusableElement) => void;
14
14
  }>;
15
15
  /**
16
- * Focus manager logic
16
+ * Focus manager logic.
17
17
  */
18
18
  declare const FocusManager: FC<{
19
19
  children: ReactNode;
20
+ onClose: (e: KeyboardEvent) => void;
20
21
  }>;
21
22
  export default FocusManager;
@@ -7,5 +7,5 @@ import { MenuWrapperProps } from '../../types';
7
7
  * if a CheckboxItem or RadioItem is clicked.
8
8
  * It also sets focus to the first menu item when opened.
9
9
  */
10
- declare const MenuWrapper: ({ children, isLoading, maxHeight, maxWidth, onClose, onUpdate, statusLabel, setInitialFocusRef, spacing, testId, }: MenuWrapperProps) => jsx.JSX.Element;
10
+ declare const MenuWrapper: ({ children, isLoading, maxHeight, maxWidth, onClose, onUpdate, statusLabel, setInitialFocusRef, shouldRenderToParent, spacing, testId, isTriggeredUsingKeyboard, autoFocus, }: MenuWrapperProps) => jsx.JSX.Element;
11
11
  export default MenuWrapper;
@@ -1,2 +1,2 @@
1
1
  import { FocusableElement } from '../../types';
2
- export default function handleFocus(refs: Array<FocusableElement>, isLayerDisabled: () => boolean): (e: KeyboardEvent) => void;
2
+ export default function handleFocus(refs: Array<FocusableElement>, isLayerDisabled: () => boolean, onClose: (e: KeyboardEvent) => void): (e: KeyboardEvent) => void;
@@ -2,7 +2,7 @@ import { KeyboardEvent, MouseEvent, ReactElement, ReactNode, Ref } from 'react';
2
2
  import type { CustomItemComponentProps, CustomItemProps, MenuGroupProps, SectionProps } from '@atlaskit/menu/types';
3
3
  import type { ContentProps, TriggerProps } from '@atlaskit/popup/types';
4
4
  export type FocusableElement = HTMLAnchorElement | HTMLButtonElement;
5
- export type Action = 'next' | 'prev' | 'first' | 'last';
5
+ export type Action = 'next' | 'prev' | 'first' | 'last' | 'tab';
6
6
  export type Placement = 'auto-start' | 'auto' | 'auto-end' | 'top-start' | 'top' | 'top-end' | 'right-start' | 'right' | 'right-end' | 'bottom-end' | 'bottom' | 'bottom-start' | 'left-end' | 'left' | 'left-start';
7
7
  export type ItemId = string;
8
8
  export type GroupId = string;
@@ -49,6 +49,9 @@ export interface MenuWrapperProps extends MenuGroupProps {
49
49
  onUpdate: ContentProps['update'];
50
50
  isLoading?: DropdownMenuProps['isLoading'];
51
51
  statusLabel?: DropdownMenuProps['statusLabel'];
52
+ shouldRenderToParent?: boolean;
53
+ isTriggeredUsingKeyboard?: boolean;
54
+ autoFocus?: boolean;
52
55
  }
53
56
  export interface DropdownMenuGroupProps extends SectionProps {
54
57
  }
@@ -1,7 +1,5 @@
1
1
  /// <reference types="react" />
2
2
  import type { DropdownMenuProps } from './types';
3
- export declare const KEY_SPACE = " ";
4
- export declare const KEY_ENTER = "Enter";
5
3
  /**
6
4
  * __Dropdown menu__
7
5
  *
@@ -13,9 +13,10 @@ export declare const FocusManagerContext: React.Context<{
13
13
  registerRef: (ref: FocusableElement) => void;
14
14
  }>;
15
15
  /**
16
- * Focus manager logic
16
+ * Focus manager logic.
17
17
  */
18
18
  declare const FocusManager: FC<{
19
19
  children: ReactNode;
20
+ onClose: (e: KeyboardEvent) => void;
20
21
  }>;
21
22
  export default FocusManager;
@@ -7,5 +7,5 @@ import { MenuWrapperProps } from '../../types';
7
7
  * if a CheckboxItem or RadioItem is clicked.
8
8
  * It also sets focus to the first menu item when opened.
9
9
  */
10
- declare const MenuWrapper: ({ children, isLoading, maxHeight, maxWidth, onClose, onUpdate, statusLabel, setInitialFocusRef, spacing, testId, }: MenuWrapperProps) => jsx.JSX.Element;
10
+ declare const MenuWrapper: ({ children, isLoading, maxHeight, maxWidth, onClose, onUpdate, statusLabel, setInitialFocusRef, shouldRenderToParent, spacing, testId, isTriggeredUsingKeyboard, autoFocus, }: MenuWrapperProps) => jsx.JSX.Element;
11
11
  export default MenuWrapper;
@@ -1,2 +1,2 @@
1
1
  import { FocusableElement } from '../../types';
2
- export default function handleFocus(refs: Array<FocusableElement>, isLayerDisabled: () => boolean): (e: KeyboardEvent) => void;
2
+ export default function handleFocus(refs: Array<FocusableElement>, isLayerDisabled: () => boolean, onClose: (e: KeyboardEvent) => void): (e: KeyboardEvent) => void;
@@ -2,7 +2,7 @@ import { KeyboardEvent, MouseEvent, ReactElement, ReactNode, Ref } from 'react';
2
2
  import type { CustomItemComponentProps, CustomItemProps, MenuGroupProps, SectionProps } from '@atlaskit/menu/types';
3
3
  import type { ContentProps, TriggerProps } from '@atlaskit/popup/types';
4
4
  export type FocusableElement = HTMLAnchorElement | HTMLButtonElement;
5
- export type Action = 'next' | 'prev' | 'first' | 'last';
5
+ export type Action = 'next' | 'prev' | 'first' | 'last' | 'tab';
6
6
  export type Placement = 'auto-start' | 'auto' | 'auto-end' | 'top-start' | 'top' | 'top-end' | 'right-start' | 'right' | 'right-end' | 'bottom-end' | 'bottom' | 'bottom-start' | 'left-end' | 'left' | 'left-start';
7
7
  export type ItemId = string;
8
8
  export type GroupId = string;
@@ -49,6 +49,9 @@ export interface MenuWrapperProps extends MenuGroupProps {
49
49
  onUpdate: ContentProps['update'];
50
50
  isLoading?: DropdownMenuProps['isLoading'];
51
51
  statusLabel?: DropdownMenuProps['statusLabel'];
52
+ shouldRenderToParent?: boolean;
53
+ isTriggeredUsingKeyboard?: boolean;
54
+ autoFocus?: boolean;
52
55
  }
53
56
  export interface DropdownMenuGroupProps extends SectionProps {
54
57
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/dropdown-menu",
3
- "version": "12.5.3",
3
+ "version": "12.6.0",
4
4
  "description": "A dropdown menu displays a list of actions or options to a user.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -33,8 +33,8 @@
33
33
  "@atlaskit/layering": "^0.2.0",
34
34
  "@atlaskit/menu": "^2.1.0",
35
35
  "@atlaskit/platform-feature-flags": "^0.2.2",
36
- "@atlaskit/popup": "^1.12.0",
37
- "@atlaskit/primitives": "^3.0.0",
36
+ "@atlaskit/popup": "^1.13.0",
37
+ "@atlaskit/primitives": "^3.1.0",
38
38
  "@atlaskit/spinner": "^16.0.0",
39
39
  "@atlaskit/theme": "^12.6.0",
40
40
  "@atlaskit/tokens": "^1.38.0",
@@ -56,6 +56,7 @@
56
56
  "@testing-library/dom": "^8.17.1",
57
57
  "@testing-library/react": "^12.1.5",
58
58
  "@testing-library/react-hooks": "^8.0.1",
59
+ "@testing-library/user-event": "^14.4.3",
59
60
  "jest-in-case": "^1.0.2",
60
61
  "jscodeshift": "^0.13.0",
61
62
  "raf-stub": "^2.0.1",
@@ -110,6 +111,9 @@
110
111
  },
111
112
  "platform.design-system-team.update-input-border-wdith_5abwv": {
112
113
  "type": "boolean"
114
+ },
115
+ "platform.design-system-team.disable-focus-lock-in-popup_7kb4d": {
116
+ "type": "boolean"
113
117
  }
114
118
  },
115
119
  "homepage": "https://atlassian.design/components/dropdown-menu/",