@atlaskit/dropdown-menu 12.17.3 → 12.18.1

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,22 @@
1
1
  # @atlaskit/dropdown-menu
2
2
 
3
+ ## 12.18.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#138585](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/138585)
8
+ [`b72c2c7f9a2fd`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/b72c2c7f9a2fd) -
9
+ Support to close sibling dropdown menu under feature flag
10
+ - Updated dependencies
11
+
12
+ ## 12.18.0
13
+
14
+ ### Minor Changes
15
+
16
+ - [#133919](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/133919)
17
+ [`8b25fdc44ca38`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/8b25fdc44ca38) -
18
+ Added return focus to trigger after item selection
19
+
3
20
  ## 12.17.3
4
21
 
5
22
  ### Patch Changes
@@ -14,7 +14,7 @@ var _buttonItem = _interopRequireDefault(require("@atlaskit/menu/button-item"));
14
14
  var _customItem = _interopRequireDefault(require("@atlaskit/menu/custom-item"));
15
15
  var _linkItem = _interopRequireDefault(require("@atlaskit/menu/link-item"));
16
16
  var _useRegisterItemWithFocusManager = _interopRequireDefault(require("./internal/hooks/use-register-item-with-focus-manager"));
17
- var _excluded = ["children", "component", "description", "elemAfter", "elemBefore", "href", "isDisabled", "isSelected", "onClick", "rel", "shouldDescriptionWrap", "shouldTitleWrap", "target", "testId", "UNSAFE_shouldDisableRouterLink"];
17
+ var _excluded = ["children", "component", "description", "elemAfter", "elemBefore", "href", "isDisabled", "isSelected", "onClick", "rel", "shouldDescriptionWrap", "shouldTitleWrap", "target", "testId", "UNSAFE_shouldDisableRouterLink", "returnFocusRef"];
18
18
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
19
19
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
20
20
  /**
@@ -44,8 +44,17 @@ var DropdownMenuItem = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref)
44
44
  target = _ref.target,
45
45
  testId = _ref.testId,
46
46
  UNSAFE_shouldDisableRouterLink = _ref.UNSAFE_shouldDisableRouterLink,
47
+ returnFocusRef = _ref.returnFocusRef,
47
48
  rest = (0, _objectWithoutProperties2.default)(_ref, _excluded);
48
49
  var itemRef = (0, _useRegisterItemWithFocusManager.default)();
50
+ var handleItemClick = (0, _react.useCallback)(function (event) {
51
+ if (returnFocusRef !== null && returnFocusRef !== void 0 && returnFocusRef.current) {
52
+ returnFocusRef.current.focus();
53
+ }
54
+ if (onClick) {
55
+ onClick(event);
56
+ }
57
+ }, [onClick, returnFocusRef]);
49
58
  if (component) {
50
59
  return /*#__PURE__*/_react.default.createElement(_customItem.default, (0, _extends2.default)({
51
60
  component: component,
@@ -54,7 +63,7 @@ var DropdownMenuItem = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref)
54
63
  iconBefore: elemBefore,
55
64
  isDisabled: isDisabled,
56
65
  isSelected: isSelected,
57
- onClick: onClick,
66
+ onClick: handleItemClick,
58
67
  ref: (0, _mergeRefs.default)([ref, itemRef]),
59
68
  shouldDescriptionWrap: shouldDescriptionWrap,
60
69
  shouldTitleWrap: shouldTitleWrap,
@@ -76,7 +85,7 @@ var DropdownMenuItem = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref)
76
85
  iconBefore: elemBefore,
77
86
  isDisabled: isDisabled,
78
87
  isSelected: isSelected,
79
- onClick: onClick,
88
+ onClick: handleItemClick,
80
89
  ref: (0, _mergeRefs.default)([ref, itemRef]),
81
90
  rel: rel,
82
91
  role: "menuitem",
@@ -94,7 +103,7 @@ var DropdownMenuItem = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref)
94
103
  iconBefore: elemBefore,
95
104
  isDisabled: isDisabled,
96
105
  isSelected: isSelected,
97
- onClick: onClick,
106
+ onClick: handleItemClick,
98
107
  ref: (0, _mergeRefs.default)([ref, itemRef]),
99
108
  role: "menuitem",
100
109
  shouldDescriptionWrap: shouldDescriptionWrap,
@@ -23,12 +23,11 @@ var _popup = _interopRequireDefault(require("@atlaskit/popup"));
23
23
  var _constants = require("@atlaskit/theme/constants");
24
24
  var _focusManager = _interopRequireDefault(require("./internal/components/focus-manager"));
25
25
  var _menuWrapper = _interopRequireDefault(require("./internal/components/menu-wrapper"));
26
+ var _dropdownMenuContext = require("./internal/context/dropdown-menu-context");
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"];
30
- /* eslint-disable import/order */
31
- // eslint-disable-next-line @atlaskit/design-system/no-deprecated-imports
30
+ var _excluded = ["ref", "aria-controls", "aria-expanded", "aria-haspopup"]; // eslint-disable-next-line @atlaskit/design-system/no-deprecated-imports
32
31
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
33
32
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
34
33
  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; }
@@ -105,6 +104,7 @@ var DropdownMenu = function DropdownMenu(_ref) {
105
104
  _useControlledState2 = (0, _slicedToArray2.default)(_useControlledState, 2),
106
105
  isLocalOpen = _useControlledState2[0],
107
106
  setLocalIsOpen = _useControlledState2[1];
107
+ var triggerRef = (0, _react.useRef)(null);
108
108
  var _useState = (0, _react.useState)(false),
109
109
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
110
110
  isTriggeredUsingKeyboard = _useState2[0],
@@ -147,13 +147,22 @@ var DropdownMenu = function DropdownMenu(_ref) {
147
147
  event: event
148
148
  });
149
149
  }, [itemRef, onOpenChange, isLocalOpen, setLocalIsOpen]);
150
- var handleOnClose = (0, _react.useCallback)(function (event) {
150
+ var handleOnClose = (0, _react.useCallback)(function (event, currentLevel) {
151
151
  var _event$target, _event$target$closest;
152
152
  if (event.key !== 'Escape' && event.key !== 'Tab' && (_event$target = event.target) !== null && _event$target !== void 0 && (_event$target$closest = _event$target.closest) !== null && _event$target$closest !== void 0 && _event$target$closest.call(_event$target, "[id^=".concat(_useGeneratedId.PREFIX, "] [aria-haspopup]"))) {
153
+ var _itemRef$current2;
153
154
  // Check if it is within dropdown and it is a trigger button
154
155
  // if it is a nested dropdown, clicking trigger won't close the dropdown
155
156
  // Dropdown can be closed by pressing Escape, Tab or Shift + Tab
156
- return;
157
+ if (!currentLevel) {
158
+ return;
159
+ }
160
+ // if currentLevel is provided, we will compare with the given item's level
161
+ // when it is available and larger than currentLevel, we will proceed the close behavior
162
+ var toCloseLevel = (_itemRef$current2 = itemRef.current) === null || _itemRef$current2 === void 0 ? void 0 : _itemRef$current2.dataset['ds-Level'];
163
+ if (toCloseLevel && Number(toCloseLevel) < currentLevel) {
164
+ return;
165
+ }
157
166
  }
158
167
 
159
168
  // transfer focus to the element specified by ref
@@ -166,8 +175,13 @@ var DropdownMenu = function DropdownMenu(_ref) {
166
175
  });
167
176
  } else if (event.key === 'Tab' && event.shiftKey || event.key === 'Escape') {
168
177
  requestAnimationFrame(function () {
169
- var _itemRef$current2;
170
- (_itemRef$current2 = itemRef.current) === null || _itemRef$current2 === void 0 || _itemRef$current2.focus();
178
+ var _itemRef$current3;
179
+ (_itemRef$current3 = itemRef.current) === null || _itemRef$current3 === void 0 || _itemRef$current3.focus();
180
+ });
181
+ } else if (triggerRef.current) {
182
+ requestAnimationFrame(function () {
183
+ var _triggerRef$current;
184
+ (_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 || _triggerRef$current.focus();
171
185
  });
172
186
  }
173
187
  var newValue = false;
@@ -233,7 +247,11 @@ var DropdownMenu = function DropdownMenu(_ref) {
233
247
  } : {
234
248
  shouldRenderToParent: shouldRenderToParent
235
249
  };
236
- return /*#__PURE__*/_react.default.createElement(_selectionStore.default, null, /*#__PURE__*/_react.default.createElement(_popup.default, (0, _extends2.default)({
250
+ return /*#__PURE__*/_react.default.createElement(_selectionStore.default, null, /*#__PURE__*/_react.default.createElement(_dropdownMenuContext.DropdownMenuProvider, {
251
+ value: {
252
+ returnFocusRef: triggerRef
253
+ }
254
+ }, /*#__PURE__*/_react.default.createElement(_popup.default, (0, _extends2.default)({
237
255
  id: isLocalOpen ? id : undefined,
238
256
  shouldFlip: shouldFlip,
239
257
  isOpen: isLocalOpen,
@@ -257,14 +275,14 @@ var DropdownMenu = function DropdownMenu(_ref) {
257
275
  'aria-expanded': ariaExpanded,
258
276
  'aria-haspopup': ariaHasPopup
259
277
  }, rest), bindFocus), {}, {
260
- triggerRef: (0, _mergeRefs.default)([ref, itemRef]),
278
+ triggerRef: (0, _mergeRefs.default)([ref, triggerRef, itemRef]),
261
279
  isSelected: isLocalOpen,
262
280
  onClick: handleTriggerClicked,
263
281
  testId: testId && "".concat(testId, "--trigger")
264
282
  }));
265
283
  }
266
284
  return /*#__PURE__*/_react.default.createElement(_new.default, (0, _extends2.default)({}, bindFocus, {
267
- ref: (0, _mergeRefs.default)([ref, itemRef]),
285
+ ref: (0, _mergeRefs.default)([ref, triggerRef, itemRef]),
268
286
  "aria-controls": ariaControls,
269
287
  "aria-expanded": ariaExpanded,
270
288
  "aria-haspopup": ariaHasPopup,
@@ -295,6 +313,6 @@ var DropdownMenu = function DropdownMenu(_ref) {
295
313
  testId: testId && "".concat(testId, "--menu-wrapper")
296
314
  }, children));
297
315
  }
298
- })));
316
+ }))));
299
317
  };
300
318
  var _default = exports.default = DropdownMenu;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DropdownMenuProvider = void 0;
7
+ var _react = require("react");
8
+ var DropdownMenuContext = /*#__PURE__*/(0, _react.createContext)(undefined);
9
+
10
+ // TODO: Fill in the component {description} and ensure links point to the correct {packageName} location.
11
+ // Remove links that the component does not have (such as usage). If there are no links remove them all.
12
+ /**
13
+ * __Dropdown menu provider__
14
+ *
15
+ * A dropdown menu provider {description}.
16
+ *
17
+ * - [Examples](https://atlassian.design/components/{packageName}/examples)
18
+ * - [Code](https://atlassian.design/components/{packageName}/code)
19
+ * - [Usage](https://atlassian.design/components/{packageName}/usage)
20
+ */
21
+ var DropdownMenuProvider = exports.DropdownMenuProvider = DropdownMenuContext.Provider;
@@ -1,5 +1,5 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
- import React, { forwardRef } from 'react';
2
+ import React, { forwardRef, useCallback } from 'react';
3
3
  import mergeRefs from '@atlaskit/ds-lib/merge-refs';
4
4
  import ButtonItem from '@atlaskit/menu/button-item';
5
5
  import CustomItem from '@atlaskit/menu/custom-item';
@@ -30,9 +30,18 @@ const DropdownMenuItem = /*#__PURE__*/forwardRef(({
30
30
  target,
31
31
  testId,
32
32
  UNSAFE_shouldDisableRouterLink,
33
+ returnFocusRef,
33
34
  ...rest
34
35
  }, ref) => {
35
36
  const itemRef = useRegisterItemWithFocusManager();
37
+ const handleItemClick = useCallback(event => {
38
+ if (returnFocusRef !== null && returnFocusRef !== void 0 && returnFocusRef.current) {
39
+ returnFocusRef.current.focus();
40
+ }
41
+ if (onClick) {
42
+ onClick(event);
43
+ }
44
+ }, [onClick, returnFocusRef]);
36
45
  if (component) {
37
46
  return /*#__PURE__*/React.createElement(CustomItem, _extends({
38
47
  component: component,
@@ -41,7 +50,7 @@ const DropdownMenuItem = /*#__PURE__*/forwardRef(({
41
50
  iconBefore: elemBefore,
42
51
  isDisabled: isDisabled,
43
52
  isSelected: isSelected,
44
- onClick: onClick,
53
+ onClick: handleItemClick,
45
54
  ref: mergeRefs([ref, itemRef]),
46
55
  shouldDescriptionWrap: shouldDescriptionWrap,
47
56
  shouldTitleWrap: shouldTitleWrap,
@@ -63,7 +72,7 @@ const DropdownMenuItem = /*#__PURE__*/forwardRef(({
63
72
  iconBefore: elemBefore,
64
73
  isDisabled: isDisabled,
65
74
  isSelected: isSelected,
66
- onClick: onClick,
75
+ onClick: handleItemClick,
67
76
  ref: mergeRefs([ref, itemRef]),
68
77
  rel: rel,
69
78
  role: "menuitem",
@@ -81,7 +90,7 @@ const DropdownMenuItem = /*#__PURE__*/forwardRef(({
81
90
  iconBefore: elemBefore,
82
91
  isDisabled: isDisabled,
83
92
  isSelected: isSelected,
84
- onClick: onClick,
93
+ onClick: handleItemClick,
85
94
  ref: mergeRefs([ref, itemRef]),
86
95
  role: "menuitem",
87
96
  shouldDescriptionWrap: shouldDescriptionWrap,
@@ -1,6 +1,5 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
- /* eslint-disable import/order */
3
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
3
  import { bind } from 'bind-event-listener';
5
4
  import Button from '@atlaskit/button/new';
6
5
  import { KEY_DOWN, KEY_ENTER, KEY_SPACE, KEY_TAB } from '@atlaskit/ds-lib/keycodes';
@@ -14,6 +13,7 @@ import Popup from '@atlaskit/popup';
14
13
  import { gridSize as gridSizeFn, layers } from '@atlaskit/theme/constants';
15
14
  import FocusManager from './internal/components/focus-manager';
16
15
  import MenuWrapper from './internal/components/menu-wrapper';
16
+ import { DropdownMenuProvider } from './internal/context/dropdown-menu-context';
17
17
  import SelectionStore from './internal/context/selection-store';
18
18
  import useRegisterItemWithFocusManager from './internal/hooks/use-register-item-with-focus-manager';
19
19
  import useGeneratedId, { PREFIX } from './internal/utils/use-generated-id';
@@ -76,6 +76,7 @@ const DropdownMenu = ({
76
76
  label
77
77
  }) => {
78
78
  const [isLocalOpen, setLocalIsOpen] = useControlledState(isOpen, () => defaultOpen);
79
+ const triggerRef = useRef(null);
79
80
  const [isTriggeredUsingKeyboard, setTriggeredUsingKeyboard] = useState(false);
80
81
  const id = useGeneratedId();
81
82
  const itemRef = useRegisterItemWithFocusManager();
@@ -115,13 +116,22 @@ const DropdownMenu = ({
115
116
  event
116
117
  });
117
118
  }, [itemRef, onOpenChange, isLocalOpen, setLocalIsOpen]);
118
- const handleOnClose = useCallback(event => {
119
+ const handleOnClose = useCallback((event, currentLevel) => {
119
120
  var _event$target, _event$target$closest;
120
121
  if (event.key !== 'Escape' && event.key !== 'Tab' && (_event$target = event.target) !== null && _event$target !== void 0 && (_event$target$closest = _event$target.closest) !== null && _event$target$closest !== void 0 && _event$target$closest.call(_event$target, `[id^=${PREFIX}] [aria-haspopup]`)) {
122
+ var _itemRef$current2;
121
123
  // Check if it is within dropdown and it is a trigger button
122
124
  // if it is a nested dropdown, clicking trigger won't close the dropdown
123
125
  // Dropdown can be closed by pressing Escape, Tab or Shift + Tab
124
- return;
126
+ if (!currentLevel) {
127
+ return;
128
+ }
129
+ // if currentLevel is provided, we will compare with the given item's level
130
+ // when it is available and larger than currentLevel, we will proceed the close behavior
131
+ const toCloseLevel = (_itemRef$current2 = itemRef.current) === null || _itemRef$current2 === void 0 ? void 0 : _itemRef$current2.dataset['ds-Level'];
132
+ if (toCloseLevel && Number(toCloseLevel) < currentLevel) {
133
+ return;
134
+ }
125
135
  }
126
136
 
127
137
  // transfer focus to the element specified by ref
@@ -134,8 +144,13 @@ const DropdownMenu = ({
134
144
  });
135
145
  } else if (event.key === 'Tab' && event.shiftKey || event.key === 'Escape') {
136
146
  requestAnimationFrame(() => {
137
- var _itemRef$current2;
138
- (_itemRef$current2 = itemRef.current) === null || _itemRef$current2 === void 0 ? void 0 : _itemRef$current2.focus();
147
+ var _itemRef$current3;
148
+ (_itemRef$current3 = itemRef.current) === null || _itemRef$current3 === void 0 ? void 0 : _itemRef$current3.focus();
149
+ });
150
+ } else if (triggerRef.current) {
151
+ requestAnimationFrame(() => {
152
+ var _triggerRef$current;
153
+ (_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 ? void 0 : _triggerRef$current.focus();
139
154
  });
140
155
  }
141
156
  const newValue = false;
@@ -202,7 +217,11 @@ const DropdownMenu = ({
202
217
  } : {
203
218
  shouldRenderToParent
204
219
  };
205
- return /*#__PURE__*/React.createElement(SelectionStore, null, /*#__PURE__*/React.createElement(Popup, _extends({
220
+ return /*#__PURE__*/React.createElement(SelectionStore, null, /*#__PURE__*/React.createElement(DropdownMenuProvider, {
221
+ value: {
222
+ returnFocusRef: triggerRef
223
+ }
224
+ }, /*#__PURE__*/React.createElement(Popup, _extends({
206
225
  id: isLocalOpen ? id : undefined,
207
226
  shouldFlip: shouldFlip,
208
227
  isOpen: isLocalOpen,
@@ -229,14 +248,14 @@ const DropdownMenu = ({
229
248
  'aria-haspopup': ariaHasPopup,
230
249
  ...rest,
231
250
  ...bindFocus,
232
- triggerRef: mergeRefs([ref, itemRef]),
251
+ triggerRef: mergeRefs([ref, triggerRef, itemRef]),
233
252
  isSelected: isLocalOpen,
234
253
  onClick: handleTriggerClicked,
235
254
  testId: testId && `${testId}--trigger`
236
255
  });
237
256
  }
238
257
  return /*#__PURE__*/React.createElement(Button, _extends({}, bindFocus, {
239
- ref: mergeRefs([ref, itemRef]),
258
+ ref: mergeRefs([ref, triggerRef, itemRef]),
240
259
  "aria-controls": ariaControls,
241
260
  "aria-expanded": ariaExpanded,
242
261
  "aria-haspopup": ariaHasPopup,
@@ -266,6 +285,6 @@ const DropdownMenu = ({
266
285
  autoFocus: autoFocus,
267
286
  testId: testId && `${testId}--menu-wrapper`
268
287
  }, children))
269
- })));
288
+ }))));
270
289
  };
271
290
  export default DropdownMenu;
@@ -0,0 +1,15 @@
1
+ import { createContext } from 'react';
2
+ const DropdownMenuContext = /*#__PURE__*/createContext(undefined);
3
+
4
+ // TODO: Fill in the component {description} and ensure links point to the correct {packageName} location.
5
+ // Remove links that the component does not have (such as usage). If there are no links remove them all.
6
+ /**
7
+ * __Dropdown menu provider__
8
+ *
9
+ * A dropdown menu provider {description}.
10
+ *
11
+ * - [Examples](https://atlassian.design/components/{packageName}/examples)
12
+ * - [Code](https://atlassian.design/components/{packageName}/code)
13
+ * - [Usage](https://atlassian.design/components/{packageName}/usage)
14
+ */
15
+ export const DropdownMenuProvider = DropdownMenuContext.Provider;
@@ -1,7 +1,7 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
- var _excluded = ["children", "component", "description", "elemAfter", "elemBefore", "href", "isDisabled", "isSelected", "onClick", "rel", "shouldDescriptionWrap", "shouldTitleWrap", "target", "testId", "UNSAFE_shouldDisableRouterLink"];
4
- import React, { forwardRef } from 'react';
3
+ var _excluded = ["children", "component", "description", "elemAfter", "elemBefore", "href", "isDisabled", "isSelected", "onClick", "rel", "shouldDescriptionWrap", "shouldTitleWrap", "target", "testId", "UNSAFE_shouldDisableRouterLink", "returnFocusRef"];
4
+ import React, { forwardRef, useCallback } from 'react';
5
5
  import mergeRefs from '@atlaskit/ds-lib/merge-refs';
6
6
  import ButtonItem from '@atlaskit/menu/button-item';
7
7
  import CustomItem from '@atlaskit/menu/custom-item';
@@ -34,8 +34,17 @@ var DropdownMenuItem = /*#__PURE__*/forwardRef(function (_ref, ref) {
34
34
  target = _ref.target,
35
35
  testId = _ref.testId,
36
36
  UNSAFE_shouldDisableRouterLink = _ref.UNSAFE_shouldDisableRouterLink,
37
+ returnFocusRef = _ref.returnFocusRef,
37
38
  rest = _objectWithoutProperties(_ref, _excluded);
38
39
  var itemRef = useRegisterItemWithFocusManager();
40
+ var handleItemClick = useCallback(function (event) {
41
+ if (returnFocusRef !== null && returnFocusRef !== void 0 && returnFocusRef.current) {
42
+ returnFocusRef.current.focus();
43
+ }
44
+ if (onClick) {
45
+ onClick(event);
46
+ }
47
+ }, [onClick, returnFocusRef]);
39
48
  if (component) {
40
49
  return /*#__PURE__*/React.createElement(CustomItem, _extends({
41
50
  component: component,
@@ -44,7 +53,7 @@ var DropdownMenuItem = /*#__PURE__*/forwardRef(function (_ref, ref) {
44
53
  iconBefore: elemBefore,
45
54
  isDisabled: isDisabled,
46
55
  isSelected: isSelected,
47
- onClick: onClick,
56
+ onClick: handleItemClick,
48
57
  ref: mergeRefs([ref, itemRef]),
49
58
  shouldDescriptionWrap: shouldDescriptionWrap,
50
59
  shouldTitleWrap: shouldTitleWrap,
@@ -66,7 +75,7 @@ var DropdownMenuItem = /*#__PURE__*/forwardRef(function (_ref, ref) {
66
75
  iconBefore: elemBefore,
67
76
  isDisabled: isDisabled,
68
77
  isSelected: isSelected,
69
- onClick: onClick,
78
+ onClick: handleItemClick,
70
79
  ref: mergeRefs([ref, itemRef]),
71
80
  rel: rel,
72
81
  role: "menuitem",
@@ -84,7 +93,7 @@ var DropdownMenuItem = /*#__PURE__*/forwardRef(function (_ref, ref) {
84
93
  iconBefore: elemBefore,
85
94
  isDisabled: isDisabled,
86
95
  isSelected: isSelected,
87
- onClick: onClick,
96
+ onClick: handleItemClick,
88
97
  ref: mergeRefs([ref, itemRef]),
89
98
  role: "menuitem",
90
99
  shouldDescriptionWrap: shouldDescriptionWrap,
@@ -5,8 +5,7 @@ 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 */
9
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
8
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
10
9
  import { bind } from 'bind-event-listener';
11
10
  import Button from '@atlaskit/button/new';
12
11
  import { KEY_DOWN, KEY_ENTER, KEY_SPACE, KEY_TAB } from '@atlaskit/ds-lib/keycodes';
@@ -20,6 +19,7 @@ import Popup from '@atlaskit/popup';
20
19
  import { gridSize as gridSizeFn, layers } from '@atlaskit/theme/constants';
21
20
  import FocusManager from './internal/components/focus-manager';
22
21
  import MenuWrapper from './internal/components/menu-wrapper';
22
+ import { DropdownMenuProvider } from './internal/context/dropdown-menu-context';
23
23
  import SelectionStore from './internal/context/selection-store';
24
24
  import useRegisterItemWithFocusManager from './internal/hooks/use-register-item-with-focus-manager';
25
25
  import useGeneratedId, { PREFIX } from './internal/utils/use-generated-id';
@@ -95,6 +95,7 @@ var DropdownMenu = function DropdownMenu(_ref) {
95
95
  _useControlledState2 = _slicedToArray(_useControlledState, 2),
96
96
  isLocalOpen = _useControlledState2[0],
97
97
  setLocalIsOpen = _useControlledState2[1];
98
+ var triggerRef = useRef(null);
98
99
  var _useState = useState(false),
99
100
  _useState2 = _slicedToArray(_useState, 2),
100
101
  isTriggeredUsingKeyboard = _useState2[0],
@@ -137,13 +138,22 @@ var DropdownMenu = function DropdownMenu(_ref) {
137
138
  event: event
138
139
  });
139
140
  }, [itemRef, onOpenChange, isLocalOpen, setLocalIsOpen]);
140
- var handleOnClose = useCallback(function (event) {
141
+ var handleOnClose = useCallback(function (event, currentLevel) {
141
142
  var _event$target, _event$target$closest;
142
143
  if (event.key !== 'Escape' && event.key !== 'Tab' && (_event$target = event.target) !== null && _event$target !== void 0 && (_event$target$closest = _event$target.closest) !== null && _event$target$closest !== void 0 && _event$target$closest.call(_event$target, "[id^=".concat(PREFIX, "] [aria-haspopup]"))) {
144
+ var _itemRef$current2;
143
145
  // Check if it is within dropdown and it is a trigger button
144
146
  // if it is a nested dropdown, clicking trigger won't close the dropdown
145
147
  // Dropdown can be closed by pressing Escape, Tab or Shift + Tab
146
- return;
148
+ if (!currentLevel) {
149
+ return;
150
+ }
151
+ // if currentLevel is provided, we will compare with the given item's level
152
+ // when it is available and larger than currentLevel, we will proceed the close behavior
153
+ var toCloseLevel = (_itemRef$current2 = itemRef.current) === null || _itemRef$current2 === void 0 ? void 0 : _itemRef$current2.dataset['ds-Level'];
154
+ if (toCloseLevel && Number(toCloseLevel) < currentLevel) {
155
+ return;
156
+ }
147
157
  }
148
158
 
149
159
  // transfer focus to the element specified by ref
@@ -156,8 +166,13 @@ var DropdownMenu = function DropdownMenu(_ref) {
156
166
  });
157
167
  } else if (event.key === 'Tab' && event.shiftKey || event.key === 'Escape') {
158
168
  requestAnimationFrame(function () {
159
- var _itemRef$current2;
160
- (_itemRef$current2 = itemRef.current) === null || _itemRef$current2 === void 0 || _itemRef$current2.focus();
169
+ var _itemRef$current3;
170
+ (_itemRef$current3 = itemRef.current) === null || _itemRef$current3 === void 0 || _itemRef$current3.focus();
171
+ });
172
+ } else if (triggerRef.current) {
173
+ requestAnimationFrame(function () {
174
+ var _triggerRef$current;
175
+ (_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 || _triggerRef$current.focus();
161
176
  });
162
177
  }
163
178
  var newValue = false;
@@ -223,7 +238,11 @@ var DropdownMenu = function DropdownMenu(_ref) {
223
238
  } : {
224
239
  shouldRenderToParent: shouldRenderToParent
225
240
  };
226
- return /*#__PURE__*/React.createElement(SelectionStore, null, /*#__PURE__*/React.createElement(Popup, _extends({
241
+ return /*#__PURE__*/React.createElement(SelectionStore, null, /*#__PURE__*/React.createElement(DropdownMenuProvider, {
242
+ value: {
243
+ returnFocusRef: triggerRef
244
+ }
245
+ }, /*#__PURE__*/React.createElement(Popup, _extends({
227
246
  id: isLocalOpen ? id : undefined,
228
247
  shouldFlip: shouldFlip,
229
248
  isOpen: isLocalOpen,
@@ -247,14 +266,14 @@ var DropdownMenu = function DropdownMenu(_ref) {
247
266
  'aria-expanded': ariaExpanded,
248
267
  'aria-haspopup': ariaHasPopup
249
268
  }, rest), bindFocus), {}, {
250
- triggerRef: mergeRefs([ref, itemRef]),
269
+ triggerRef: mergeRefs([ref, triggerRef, itemRef]),
251
270
  isSelected: isLocalOpen,
252
271
  onClick: handleTriggerClicked,
253
272
  testId: testId && "".concat(testId, "--trigger")
254
273
  }));
255
274
  }
256
275
  return /*#__PURE__*/React.createElement(Button, _extends({}, bindFocus, {
257
- ref: mergeRefs([ref, itemRef]),
276
+ ref: mergeRefs([ref, triggerRef, itemRef]),
258
277
  "aria-controls": ariaControls,
259
278
  "aria-expanded": ariaExpanded,
260
279
  "aria-haspopup": ariaHasPopup,
@@ -285,6 +304,6 @@ var DropdownMenu = function DropdownMenu(_ref) {
285
304
  testId: testId && "".concat(testId, "--menu-wrapper")
286
305
  }, children));
287
306
  }
288
- })));
307
+ }))));
289
308
  };
290
309
  export default DropdownMenu;
@@ -0,0 +1,15 @@
1
+ import { createContext } from 'react';
2
+ var DropdownMenuContext = /*#__PURE__*/createContext(undefined);
3
+
4
+ // TODO: Fill in the component {description} and ensure links point to the correct {packageName} location.
5
+ // Remove links that the component does not have (such as usage). If there are no links remove them all.
6
+ /**
7
+ * __Dropdown menu provider__
8
+ *
9
+ * A dropdown menu provider {description}.
10
+ *
11
+ * - [Examples](https://atlassian.design/components/{packageName}/examples)
12
+ * - [Code](https://atlassian.design/components/{packageName}/code)
13
+ * - [Usage](https://atlassian.design/components/{packageName}/usage)
14
+ */
15
+ export var DropdownMenuProvider = DropdownMenuContext.Provider;
@@ -0,0 +1,16 @@
1
+ import { type RefObject } from 'react';
2
+ interface DropdownMenuContext {
3
+ returnFocusRef: RefObject<HTMLElement> | null;
4
+ }
5
+ declare const DropdownMenuContext: import("react").Context<DropdownMenuContext | undefined>;
6
+ /**
7
+ * __Dropdown menu provider__
8
+ *
9
+ * A dropdown menu provider {description}.
10
+ *
11
+ * - [Examples](https://atlassian.design/components/{packageName}/examples)
12
+ * - [Code](https://atlassian.design/components/{packageName}/code)
13
+ * - [Usage](https://atlassian.design/components/{packageName}/usage)
14
+ */
15
+ export declare const DropdownMenuProvider: import("react").Provider<DropdownMenuContext | undefined>;
16
+ export {};
@@ -238,6 +238,10 @@ export interface DropdownItemProps {
238
238
  * Marked as "unsafe" because ideally, router links should be used for all internal links.
239
239
  */
240
240
  UNSAFE_shouldDisableRouterLink?: boolean;
241
+ /**
242
+ * If ref is passed, focus returns to that specific ref element after dropdown item clicked.
243
+ */
244
+ returnFocusRef?: RefObject<HTMLElement>;
241
245
  }
242
246
  export interface DropdownItemCheckboxProps {
243
247
  /**
@@ -0,0 +1,16 @@
1
+ import { type RefObject } from 'react';
2
+ interface DropdownMenuContext {
3
+ returnFocusRef: RefObject<HTMLElement> | null;
4
+ }
5
+ declare const DropdownMenuContext: import("react").Context<DropdownMenuContext | undefined>;
6
+ /**
7
+ * __Dropdown menu provider__
8
+ *
9
+ * A dropdown menu provider {description}.
10
+ *
11
+ * - [Examples](https://atlassian.design/components/{packageName}/examples)
12
+ * - [Code](https://atlassian.design/components/{packageName}/code)
13
+ * - [Usage](https://atlassian.design/components/{packageName}/usage)
14
+ */
15
+ export declare const DropdownMenuProvider: import("react").Provider<DropdownMenuContext | undefined>;
16
+ export {};
@@ -238,6 +238,10 @@ export interface DropdownItemProps {
238
238
  * Marked as "unsafe" because ideally, router links should be used for all internal links.
239
239
  */
240
240
  UNSAFE_shouldDisableRouterLink?: boolean;
241
+ /**
242
+ * If ref is passed, focus returns to that specific ref element after dropdown item clicked.
243
+ */
244
+ returnFocusRef?: RefObject<HTMLElement>;
241
245
  }
242
246
  export interface DropdownItemCheckboxProps {
243
247
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/dropdown-menu",
3
- "version": "12.17.3",
3
+ "version": "12.18.1",
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/"
@@ -16,10 +16,6 @@
16
16
  "atlaskit:src": "src/index.tsx",
17
17
  "atlassian": {
18
18
  "team": "Design System Team",
19
- "releaseModel": "continuous",
20
- "productPushConsumption": [
21
- "jira"
22
- ],
23
19
  "website": {
24
20
  "name": "Dropdown menu",
25
21
  "category": "Components"
@@ -30,11 +26,11 @@
30
26
  "@atlaskit/button": "^20.1.0",
31
27
  "@atlaskit/codemod-utils": "^4.2.0",
32
28
  "@atlaskit/ds-lib": "^2.5.0",
33
- "@atlaskit/icon": "^22.14.0",
29
+ "@atlaskit/icon": "^22.16.0",
34
30
  "@atlaskit/layering": "^0.4.0",
35
31
  "@atlaskit/menu": "^2.12.0",
36
- "@atlaskit/popup": "^1.23.0",
37
- "@atlaskit/primitives": "^12.0.0",
32
+ "@atlaskit/popup": "^1.25.0",
33
+ "@atlaskit/primitives": "^12.1.0",
38
34
  "@atlaskit/spinner": "^16.3.0",
39
35
  "@atlaskit/theme": "^13.0.0",
40
36
  "@atlaskit/tokens": "^1.59.0",
@@ -52,7 +48,7 @@
52
48
  "@af/visual-regression": "*",
53
49
  "@atlaskit/app-provider": "^1.4.0",
54
50
  "@atlaskit/modal-dialog": "^12.15.0",
55
- "@atlaskit/toggle": "^13.3.0",
51
+ "@atlaskit/toggle": "^13.4.0",
56
52
  "@atlaskit/visual-regression": "*",
57
53
  "@atlassian/feature-flags-test-utils": "*",
58
54
  "@testing-library/dom": "^10.1.0",
@@ -72,6 +68,20 @@
72
68
  "react",
73
69
  "ui"
74
70
  ],
71
+ "platform-feature-flags": {
72
+ "platform_dst_popup-disable-focuslock": {
73
+ "type": "boolean",
74
+ "referenceOnly": true
75
+ },
76
+ "sibling-dropdown-close-issue": {
77
+ "type": "boolean",
78
+ "referenceOnly": true
79
+ },
80
+ "design-system-closed-all-when-click-outside": {
81
+ "type": "boolean",
82
+ "referenceOnly": true
83
+ }
84
+ },
75
85
  "techstack": {
76
86
  "@atlassian/frontend": {
77
87
  "import-structure": "atlassian-conventions"