@atlaskit/dropdown-menu 16.10.0 → 16.10.2
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 +14 -0
- package/dist/cjs/dropdown-menu-top-layer.js +58 -41
- package/dist/es2019/dropdown-menu-top-layer.js +56 -40
- package/dist/esm/dropdown-menu-top-layer.js +58 -41
- package/dist/types/dropdown-menu-top-layer.d.ts +2 -2
- package/dist/types-ts4.5/dropdown-menu-top-layer.d.ts +2 -2
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @atlaskit/dropdown-menu
|
|
2
2
|
|
|
3
|
+
## 16.10.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`021a645c6f39a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/021a645c6f39a) -
|
|
8
|
+
Update internal top-layer adopter code paths behind `platform-dst-top-layer`.
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
|
|
11
|
+
## 16.10.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies
|
|
16
|
+
|
|
3
17
|
## 16.10.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
|
@@ -25,8 +25,12 @@ var _chevronDown = _interopRequireDefault(require("@atlaskit/icon/core/chevron-d
|
|
|
25
25
|
var _menuGroup = _interopRequireDefault(require("@atlaskit/menu/menu-group"));
|
|
26
26
|
var _spinner = _interopRequireDefault(require("@atlaskit/spinner"));
|
|
27
27
|
var _animations = require("@atlaskit/top-layer/animations");
|
|
28
|
+
var _getAriaForTrigger = require("@atlaskit/top-layer/get-aria-for-trigger");
|
|
28
29
|
var _placementMap = require("@atlaskit/top-layer/placement-map");
|
|
29
|
-
var
|
|
30
|
+
var _popover = require("@atlaskit/top-layer/popover");
|
|
31
|
+
var _useAnchorPosition = require("@atlaskit/top-layer/use-anchor-position");
|
|
32
|
+
var _usePopoverId = require("@atlaskit/top-layer/use-popover-id");
|
|
33
|
+
var _useWidthFromAnchor = require("@atlaskit/top-layer/use-width-from-anchor");
|
|
30
34
|
var _selectionStore = _interopRequireDefault(require("./internal/context/selection-store"));
|
|
31
35
|
var _useArrowNavigation = require("./internal/use-arrow-navigation");
|
|
32
36
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
|
|
@@ -42,9 +46,9 @@ var animation = (0, _animations.slideAndFade)();
|
|
|
42
46
|
/**
|
|
43
47
|
* Event types produced by trigger interactions.
|
|
44
48
|
*
|
|
45
|
-
* - `React.MouseEvent<Element
|
|
46
|
-
* - `React.KeyboardEvent<Element
|
|
47
|
-
* - `KeyboardEvent
|
|
49
|
+
* - `React.MouseEvent<Element>`: from the trigger's `onClick` handler
|
|
50
|
+
* - `React.KeyboardEvent<Element>`: from the trigger's `onClick` when activated via keyboard
|
|
51
|
+
* - `KeyboardEvent`: native event from the ArrowDown `bind(window, ...)` listener
|
|
48
52
|
*/
|
|
49
53
|
|
|
50
54
|
/**
|
|
@@ -89,9 +93,9 @@ function LoadingIndicator(_ref) {
|
|
|
89
93
|
* Top-layer implementation of DropdownMenu.
|
|
90
94
|
*
|
|
91
95
|
* Replaces the legacy `@atlaskit/popup` + `@atlaskit/portal` + `@atlaskit/layering` pipeline
|
|
92
|
-
* with native Popover API via `@atlaskit/top-layer`.
|
|
96
|
+
* with the native Popover API via `@atlaskit/top-layer`.
|
|
93
97
|
*
|
|
94
|
-
* What
|
|
98
|
+
* What is no longer needed:
|
|
95
99
|
* - Portal: top layer handles stacking natively
|
|
96
100
|
* - FocusLock / react-focus-lock: popover=auto provides light dismiss
|
|
97
101
|
* - z-index: top layer is always above everything
|
|
@@ -101,7 +105,7 @@ function LoadingIndicator(_ref) {
|
|
|
101
105
|
* - Fallback placements / Popper: CSS Anchor Positioning handles positioning
|
|
102
106
|
*/
|
|
103
107
|
function DropdownMenuTopLayer(_ref2) {
|
|
104
|
-
var
|
|
108
|
+
var _ref5;
|
|
105
109
|
var _ref2$autoFocus = _ref2.autoFocus,
|
|
106
110
|
autoFocus = _ref2$autoFocus === void 0 ? false : _ref2$autoFocus,
|
|
107
111
|
children = _ref2.children,
|
|
@@ -131,26 +135,38 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
131
135
|
isLocalOpen = _useControlledState2[0],
|
|
132
136
|
setLocalIsOpen = _useControlledState2[1];
|
|
133
137
|
var triggerRef = (0, _react.useRef)(null);
|
|
138
|
+
var popoverRef = (0, _react.useRef)(null);
|
|
134
139
|
var menuRef = (0, _react.useRef)(null);
|
|
135
140
|
var _useState = (0, _react.useState)(false),
|
|
136
141
|
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
|
|
137
142
|
isTriggeredUsingKeyboard = _useState2[0],
|
|
138
143
|
setTriggeredUsingKeyboard = _useState2[1];
|
|
144
|
+
var popoverId = (0, _usePopoverId.usePopoverId)();
|
|
139
145
|
var topLayerPlacement = (0, _react.useMemo)(function () {
|
|
140
146
|
return (0, _placementMap.fromLegacyPlacement)({
|
|
141
147
|
legacy: placement
|
|
142
148
|
});
|
|
143
149
|
}, [placement]);
|
|
150
|
+
(0, _useAnchorPosition.useAnchorPosition)({
|
|
151
|
+
anchorRef: triggerRef,
|
|
152
|
+
popoverRef: popoverRef,
|
|
153
|
+
placement: topLayerPlacement
|
|
154
|
+
});
|
|
155
|
+
(0, _useWidthFromAnchor.useWidthFromAnchor)({
|
|
156
|
+
mode: shouldFitContainer ? 'min-anchor' : 'none',
|
|
157
|
+
popoverRef: popoverRef,
|
|
158
|
+
anchorRef: triggerRef
|
|
159
|
+
});
|
|
144
160
|
|
|
145
|
-
//
|
|
161
|
+
// Close handling.
|
|
146
162
|
// Focus restoration is handled natively by the Popover API:
|
|
147
|
-
// - Escape
|
|
148
|
-
// - Click-outside
|
|
163
|
+
// - Escape: browser restores focus to the trigger automatically
|
|
164
|
+
// - Click-outside: browser does NOT restore (correct behavior)
|
|
149
165
|
//
|
|
150
166
|
// The only custom focus handling needed is `returnFocusRef`: when provided,
|
|
151
167
|
// we redirect focus to a different element than the trigger. We do this
|
|
152
168
|
// in the onClose callback via rAF, which runs after the browser's native
|
|
153
|
-
// restoration
|
|
169
|
+
// restoration, effectively overriding it.
|
|
154
170
|
var handleOnClose = (0, _react.useCallback)(function (_ref3) {
|
|
155
171
|
var _reason = _ref3.reason;
|
|
156
172
|
if (returnFocusRef) {
|
|
@@ -166,7 +182,7 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
166
182
|
});
|
|
167
183
|
}, [onOpenChange, returnFocusRef, setLocalIsOpen]);
|
|
168
184
|
|
|
169
|
-
//
|
|
185
|
+
// Trigger click handling.
|
|
170
186
|
var handleTriggerClicked = (0, _react.useCallback)(function (event) {
|
|
171
187
|
var newValue = !isLocalOpen;
|
|
172
188
|
setTriggeredUsingKeyboard(isKeyboardTriggered(event));
|
|
@@ -193,7 +209,7 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
193
209
|
return _noop.default;
|
|
194
210
|
}
|
|
195
211
|
|
|
196
|
-
//
|
|
212
|
+
// Do not open on ArrowDown if this trigger is inside a parent menu.
|
|
197
213
|
// Nested menus should only be opened via ArrowRight or Enter.
|
|
198
214
|
var isNestedTrigger = ((_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 ? void 0 : _triggerRef$current.closest('[role="menu"]')) != null;
|
|
199
215
|
return (0, _bindEventListener.bind)(window, {
|
|
@@ -209,7 +225,7 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
209
225
|
});
|
|
210
226
|
}, [isFocused, isLocalOpen, handleTriggerClicked]);
|
|
211
227
|
|
|
212
|
-
//
|
|
228
|
+
// Arrow navigation.
|
|
213
229
|
// useArrowNavigation handles ArrowUp/Down, Home/End, and Tab-to-close
|
|
214
230
|
// by querying focusable elements in the menu DOM container.
|
|
215
231
|
var handleArrowClose = (0, _react.useCallback)(function () {
|
|
@@ -235,7 +251,7 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
235
251
|
filter: _useArrowNavigation.isAtCurrentMenuLevel
|
|
236
252
|
});
|
|
237
253
|
|
|
238
|
-
//
|
|
254
|
+
// Auto-focus first item on open.
|
|
239
255
|
(0, _react.useEffect)(function () {
|
|
240
256
|
if (!isLocalOpen || !isTriggeredUsingKeyboard && !autoFocus) {
|
|
241
257
|
return;
|
|
@@ -252,7 +268,7 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
252
268
|
});
|
|
253
269
|
}, [isLocalOpen, isTriggeredUsingKeyboard, autoFocus]);
|
|
254
270
|
|
|
255
|
-
//
|
|
271
|
+
// Close on menu item click.
|
|
256
272
|
// Close when a regular menuitem is clicked, but not checkboxes/radios
|
|
257
273
|
// and not nested triggers (items with aria-haspopup).
|
|
258
274
|
var handleMenuClick = (0, _react.useCallback)(function (e) {
|
|
@@ -263,7 +279,7 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
263
279
|
return;
|
|
264
280
|
}
|
|
265
281
|
var isCheckboxOrRadio = menuItem.getAttribute('role') === 'menuitemcheckbox' || menuItem.getAttribute('role') === 'menuitemradio';
|
|
266
|
-
//
|
|
282
|
+
// Do not close the menu when clicking a nested trigger (aria-haspopup).
|
|
267
283
|
// The nested dropdown will handle its own open/close.
|
|
268
284
|
var isNestedTrigger = menuItem.hasAttribute('aria-haspopup');
|
|
269
285
|
if (!isCheckboxOrRadio && !isNestedTrigger) {
|
|
@@ -274,27 +290,24 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
274
290
|
});
|
|
275
291
|
}
|
|
276
292
|
}, [setLocalIsOpen, onOpenChange]);
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
_toggle = _ref5.toggle,
|
|
283
|
-
ariaAttributes = _ref5.ariaAttributes;
|
|
284
|
-
var combinedRef = (0, _mergeRefs.default)([ref, triggerRef]);
|
|
293
|
+
var ariaAttributes = (0, _getAriaForTrigger.getAriaForTrigger)({
|
|
294
|
+
role: 'menu',
|
|
295
|
+
isOpen: isLocalOpen,
|
|
296
|
+
popoverId: popoverId
|
|
297
|
+
});
|
|
285
298
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
var
|
|
299
|
+
// FUDGE(top-layer-api): cast `aria-haspopup` to the narrow shape that adopter
|
|
300
|
+
// public types expect. `@atlaskit/top-layer` types `aria-haspopup` as the wider
|
|
301
|
+
// WAI-ARIA union, but the public `CustomTriggerProps` (extending `@atlaskit/popup`
|
|
302
|
+
// `TriggerProps`) is intentionally kept narrow (`boolean | 'dialog'`) because the
|
|
303
|
+
// top-layer API surface is not yet settled. The runtime value is unchanged; only
|
|
304
|
+
// the TypeScript-visible type is narrowed at this boundary.
|
|
305
|
+
var narrowAriaAttributes = ariaAttributes;
|
|
306
|
+
var renderTrigger = function renderTrigger() {
|
|
307
|
+
var setRef = function setRef(node) {
|
|
308
|
+
triggerRef.current = node;
|
|
309
|
+
};
|
|
310
|
+
var combinedRef = (0, _mergeRefs.default)([setRef]);
|
|
298
311
|
if (typeof trigger === 'function') {
|
|
299
312
|
return trigger(_objectSpread(_objectSpread(_objectSpread({}, narrowAriaAttributes), bindFocus), {}, {
|
|
300
313
|
triggerRef: combinedRef,
|
|
@@ -317,12 +330,16 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
317
330
|
"aria-label": label,
|
|
318
331
|
interactionName: interactionName
|
|
319
332
|
}), trigger);
|
|
320
|
-
}
|
|
333
|
+
};
|
|
334
|
+
return /*#__PURE__*/React.createElement(_selectionStore.default, null, renderTrigger(), /*#__PURE__*/React.createElement(_popover.Popover, {
|
|
335
|
+
ref: popoverRef,
|
|
336
|
+
id: popoverId,
|
|
321
337
|
role: "menu",
|
|
322
|
-
label: (
|
|
338
|
+
label: (_ref5 = menuLabel !== null && menuLabel !== void 0 ? menuLabel : label) !== null && _ref5 !== void 0 ? _ref5 : typeof trigger === 'string' ? trigger : 'Menu',
|
|
323
339
|
isOpen: isLocalOpen,
|
|
340
|
+
onClose: handleOnClose,
|
|
324
341
|
animate: animation,
|
|
325
|
-
|
|
342
|
+
placement: topLayerPlacement,
|
|
326
343
|
testId: testId && "".concat(testId, "--content")
|
|
327
344
|
}, /*#__PURE__*/React.createElement("div", {
|
|
328
345
|
ref: menuRef,
|
|
@@ -339,6 +356,6 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
339
356
|
}, isLoading ? /*#__PURE__*/React.createElement(LoadingIndicator, {
|
|
340
357
|
statusLabel: statusLabel,
|
|
341
358
|
testId: testId && "".concat(testId, "--menu-wrapper--loading-indicator")
|
|
342
|
-
}) : children))))
|
|
359
|
+
}) : children))));
|
|
343
360
|
}
|
|
344
361
|
var _default = exports.default = DropdownMenuTopLayer;
|
|
@@ -15,8 +15,12 @@ import ExpandIcon from '@atlaskit/icon/core/chevron-down';
|
|
|
15
15
|
import MenuGroup from '@atlaskit/menu/menu-group';
|
|
16
16
|
import Spinner from '@atlaskit/spinner';
|
|
17
17
|
import { slideAndFade } from '@atlaskit/top-layer/animations';
|
|
18
|
+
import { getAriaForTrigger } from '@atlaskit/top-layer/get-aria-for-trigger';
|
|
18
19
|
import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map';
|
|
19
|
-
import {
|
|
20
|
+
import { Popover } from '@atlaskit/top-layer/popover';
|
|
21
|
+
import { useAnchorPosition } from '@atlaskit/top-layer/use-anchor-position';
|
|
22
|
+
import { usePopoverId } from '@atlaskit/top-layer/use-popover-id';
|
|
23
|
+
import { useWidthFromAnchor } from '@atlaskit/top-layer/use-width-from-anchor';
|
|
20
24
|
import SelectionStore from './internal/context/selection-store';
|
|
21
25
|
import { getFirstFocusable, isAtCurrentMenuLevel, useArrowNavigation } from './internal/use-arrow-navigation';
|
|
22
26
|
const MAX_HEIGHT = `calc(100vh - 16px)`;
|
|
@@ -29,9 +33,9 @@ const animation = slideAndFade();
|
|
|
29
33
|
/**
|
|
30
34
|
* Event types produced by trigger interactions.
|
|
31
35
|
*
|
|
32
|
-
* - `React.MouseEvent<Element
|
|
33
|
-
* - `React.KeyboardEvent<Element
|
|
34
|
-
* - `KeyboardEvent
|
|
36
|
+
* - `React.MouseEvent<Element>`: from the trigger's `onClick` handler
|
|
37
|
+
* - `React.KeyboardEvent<Element>`: from the trigger's `onClick` when activated via keyboard
|
|
38
|
+
* - `KeyboardEvent`: native event from the ArrowDown `bind(window, ...)` listener
|
|
35
39
|
*/
|
|
36
40
|
|
|
37
41
|
/**
|
|
@@ -76,9 +80,9 @@ function LoadingIndicator({
|
|
|
76
80
|
* Top-layer implementation of DropdownMenu.
|
|
77
81
|
*
|
|
78
82
|
* Replaces the legacy `@atlaskit/popup` + `@atlaskit/portal` + `@atlaskit/layering` pipeline
|
|
79
|
-
* with native Popover API via `@atlaskit/top-layer`.
|
|
83
|
+
* with the native Popover API via `@atlaskit/top-layer`.
|
|
80
84
|
*
|
|
81
|
-
* What
|
|
85
|
+
* What is no longer needed:
|
|
82
86
|
* - Portal: top layer handles stacking natively
|
|
83
87
|
* - FocusLock / react-focus-lock: popover=auto provides light dismiss
|
|
84
88
|
* - z-index: top layer is always above everything
|
|
@@ -108,21 +112,33 @@ function DropdownMenuTopLayer({
|
|
|
108
112
|
var _ref;
|
|
109
113
|
const [isLocalOpen, setLocalIsOpen] = useControlledState(isOpenProp, () => defaultOpen);
|
|
110
114
|
const triggerRef = useRef(null);
|
|
115
|
+
const popoverRef = useRef(null);
|
|
111
116
|
const menuRef = useRef(null);
|
|
112
117
|
const [isTriggeredUsingKeyboard, setTriggeredUsingKeyboard] = useState(false);
|
|
118
|
+
const popoverId = usePopoverId();
|
|
113
119
|
const topLayerPlacement = useMemo(() => fromLegacyPlacement({
|
|
114
120
|
legacy: placement
|
|
115
121
|
}), [placement]);
|
|
122
|
+
useAnchorPosition({
|
|
123
|
+
anchorRef: triggerRef,
|
|
124
|
+
popoverRef,
|
|
125
|
+
placement: topLayerPlacement
|
|
126
|
+
});
|
|
127
|
+
useWidthFromAnchor({
|
|
128
|
+
mode: shouldFitContainer ? 'min-anchor' : 'none',
|
|
129
|
+
popoverRef,
|
|
130
|
+
anchorRef: triggerRef
|
|
131
|
+
});
|
|
116
132
|
|
|
117
|
-
//
|
|
133
|
+
// Close handling.
|
|
118
134
|
// Focus restoration is handled natively by the Popover API:
|
|
119
|
-
// - Escape
|
|
120
|
-
// - Click-outside
|
|
135
|
+
// - Escape: browser restores focus to the trigger automatically
|
|
136
|
+
// - Click-outside: browser does NOT restore (correct behavior)
|
|
121
137
|
//
|
|
122
138
|
// The only custom focus handling needed is `returnFocusRef`: when provided,
|
|
123
139
|
// we redirect focus to a different element than the trigger. We do this
|
|
124
140
|
// in the onClose callback via rAF, which runs after the browser's native
|
|
125
|
-
// restoration
|
|
141
|
+
// restoration, effectively overriding it.
|
|
126
142
|
const handleOnClose = useCallback(({
|
|
127
143
|
reason: _reason
|
|
128
144
|
}) => {
|
|
@@ -139,7 +155,7 @@ function DropdownMenuTopLayer({
|
|
|
139
155
|
});
|
|
140
156
|
}, [onOpenChange, returnFocusRef, setLocalIsOpen]);
|
|
141
157
|
|
|
142
|
-
//
|
|
158
|
+
// Trigger click handling.
|
|
143
159
|
const handleTriggerClicked = useCallback(event => {
|
|
144
160
|
const newValue = !isLocalOpen;
|
|
145
161
|
setTriggeredUsingKeyboard(isKeyboardTriggered(event));
|
|
@@ -167,7 +183,7 @@ function DropdownMenuTopLayer({
|
|
|
167
183
|
return noop;
|
|
168
184
|
}
|
|
169
185
|
|
|
170
|
-
//
|
|
186
|
+
// Do not open on ArrowDown if this trigger is inside a parent menu.
|
|
171
187
|
// Nested menus should only be opened via ArrowRight or Enter.
|
|
172
188
|
const isNestedTrigger = ((_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 ? void 0 : _triggerRef$current.closest('[role="menu"]')) != null;
|
|
173
189
|
return bind(window, {
|
|
@@ -183,7 +199,7 @@ function DropdownMenuTopLayer({
|
|
|
183
199
|
});
|
|
184
200
|
}, [isFocused, isLocalOpen, handleTriggerClicked]);
|
|
185
201
|
|
|
186
|
-
//
|
|
202
|
+
// Arrow navigation.
|
|
187
203
|
// useArrowNavigation handles ArrowUp/Down, Home/End, and Tab-to-close
|
|
188
204
|
// by querying focusable elements in the menu DOM container.
|
|
189
205
|
const handleArrowClose = useCallback(() => {
|
|
@@ -210,7 +226,7 @@ function DropdownMenuTopLayer({
|
|
|
210
226
|
filter: isAtCurrentMenuLevel
|
|
211
227
|
});
|
|
212
228
|
|
|
213
|
-
//
|
|
229
|
+
// Auto-focus first item on open.
|
|
214
230
|
useEffect(() => {
|
|
215
231
|
if (!isLocalOpen || !isTriggeredUsingKeyboard && !autoFocus) {
|
|
216
232
|
return;
|
|
@@ -227,7 +243,7 @@ function DropdownMenuTopLayer({
|
|
|
227
243
|
});
|
|
228
244
|
}, [isLocalOpen, isTriggeredUsingKeyboard, autoFocus]);
|
|
229
245
|
|
|
230
|
-
//
|
|
246
|
+
// Close on menu item click.
|
|
231
247
|
// Close when a regular menuitem is clicked, but not checkboxes/radios
|
|
232
248
|
// and not nested triggers (items with aria-haspopup).
|
|
233
249
|
const handleMenuClick = useCallback(e => {
|
|
@@ -238,7 +254,7 @@ function DropdownMenuTopLayer({
|
|
|
238
254
|
return;
|
|
239
255
|
}
|
|
240
256
|
const isCheckboxOrRadio = menuItem.getAttribute('role') === 'menuitemcheckbox' || menuItem.getAttribute('role') === 'menuitemradio';
|
|
241
|
-
//
|
|
257
|
+
// Do not close the menu when clicking a nested trigger (aria-haspopup).
|
|
242
258
|
// The nested dropdown will handle its own open/close.
|
|
243
259
|
const isNestedTrigger = menuItem.hasAttribute('aria-haspopup');
|
|
244
260
|
if (!isCheckboxOrRadio && !isNestedTrigger) {
|
|
@@ -249,28 +265,24 @@ function DropdownMenuTopLayer({
|
|
|
249
265
|
});
|
|
250
266
|
}
|
|
251
267
|
}, [setLocalIsOpen, onOpenChange]);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
toggle: _toggle,
|
|
258
|
-
ariaAttributes
|
|
259
|
-
}) => {
|
|
260
|
-
const combinedRef = mergeRefs([ref, triggerRef]);
|
|
268
|
+
const ariaAttributes = getAriaForTrigger({
|
|
269
|
+
role: 'menu',
|
|
270
|
+
isOpen: isLocalOpen,
|
|
271
|
+
popoverId
|
|
272
|
+
});
|
|
261
273
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
+
// FUDGE(top-layer-api): cast `aria-haspopup` to the narrow shape that adopter
|
|
275
|
+
// public types expect. `@atlaskit/top-layer` types `aria-haspopup` as the wider
|
|
276
|
+
// WAI-ARIA union, but the public `CustomTriggerProps` (extending `@atlaskit/popup`
|
|
277
|
+
// `TriggerProps`) is intentionally kept narrow (`boolean | 'dialog'`) because the
|
|
278
|
+
// top-layer API surface is not yet settled. The runtime value is unchanged; only
|
|
279
|
+
// the TypeScript-visible type is narrowed at this boundary.
|
|
280
|
+
const narrowAriaAttributes = ariaAttributes;
|
|
281
|
+
const renderTrigger = () => {
|
|
282
|
+
const setRef = node => {
|
|
283
|
+
triggerRef.current = node;
|
|
284
|
+
};
|
|
285
|
+
const combinedRef = mergeRefs([setRef]);
|
|
274
286
|
if (typeof trigger === 'function') {
|
|
275
287
|
return trigger({
|
|
276
288
|
...narrowAriaAttributes,
|
|
@@ -293,12 +305,16 @@ function DropdownMenuTopLayer({
|
|
|
293
305
|
"aria-label": label,
|
|
294
306
|
interactionName: interactionName
|
|
295
307
|
}), trigger);
|
|
296
|
-
}
|
|
308
|
+
};
|
|
309
|
+
return /*#__PURE__*/React.createElement(SelectionStore, null, renderTrigger(), /*#__PURE__*/React.createElement(Popover, {
|
|
310
|
+
ref: popoverRef,
|
|
311
|
+
id: popoverId,
|
|
297
312
|
role: "menu",
|
|
298
313
|
label: (_ref = menuLabel !== null && menuLabel !== void 0 ? menuLabel : label) !== null && _ref !== void 0 ? _ref : typeof trigger === 'string' ? trigger : 'Menu',
|
|
299
314
|
isOpen: isLocalOpen,
|
|
315
|
+
onClose: handleOnClose,
|
|
300
316
|
animate: animation,
|
|
301
|
-
|
|
317
|
+
placement: topLayerPlacement,
|
|
302
318
|
testId: testId && `${testId}--content`
|
|
303
319
|
}, /*#__PURE__*/React.createElement("div", {
|
|
304
320
|
ref: menuRef,
|
|
@@ -315,6 +331,6 @@ function DropdownMenuTopLayer({
|
|
|
315
331
|
}, isLoading ? /*#__PURE__*/React.createElement(LoadingIndicator, {
|
|
316
332
|
statusLabel: statusLabel,
|
|
317
333
|
testId: testId && `${testId}--menu-wrapper--loading-indicator`
|
|
318
|
-
}) : children))))
|
|
334
|
+
}) : children))));
|
|
319
335
|
}
|
|
320
336
|
export default DropdownMenuTopLayer;
|
|
@@ -19,8 +19,12 @@ import ExpandIcon from '@atlaskit/icon/core/chevron-down';
|
|
|
19
19
|
import MenuGroup from '@atlaskit/menu/menu-group';
|
|
20
20
|
import Spinner from '@atlaskit/spinner';
|
|
21
21
|
import { slideAndFade } from '@atlaskit/top-layer/animations';
|
|
22
|
+
import { getAriaForTrigger } from '@atlaskit/top-layer/get-aria-for-trigger';
|
|
22
23
|
import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map';
|
|
23
|
-
import {
|
|
24
|
+
import { Popover } from '@atlaskit/top-layer/popover';
|
|
25
|
+
import { useAnchorPosition } from '@atlaskit/top-layer/use-anchor-position';
|
|
26
|
+
import { usePopoverId } from '@atlaskit/top-layer/use-popover-id';
|
|
27
|
+
import { useWidthFromAnchor } from '@atlaskit/top-layer/use-width-from-anchor';
|
|
24
28
|
import SelectionStore from './internal/context/selection-store';
|
|
25
29
|
import { getFirstFocusable, isAtCurrentMenuLevel, useArrowNavigation } from './internal/use-arrow-navigation';
|
|
26
30
|
var MAX_HEIGHT = "calc(100vh - 16px)";
|
|
@@ -33,9 +37,9 @@ var animation = slideAndFade();
|
|
|
33
37
|
/**
|
|
34
38
|
* Event types produced by trigger interactions.
|
|
35
39
|
*
|
|
36
|
-
* - `React.MouseEvent<Element
|
|
37
|
-
* - `React.KeyboardEvent<Element
|
|
38
|
-
* - `KeyboardEvent
|
|
40
|
+
* - `React.MouseEvent<Element>`: from the trigger's `onClick` handler
|
|
41
|
+
* - `React.KeyboardEvent<Element>`: from the trigger's `onClick` when activated via keyboard
|
|
42
|
+
* - `KeyboardEvent`: native event from the ArrowDown `bind(window, ...)` listener
|
|
39
43
|
*/
|
|
40
44
|
|
|
41
45
|
/**
|
|
@@ -80,9 +84,9 @@ function LoadingIndicator(_ref) {
|
|
|
80
84
|
* Top-layer implementation of DropdownMenu.
|
|
81
85
|
*
|
|
82
86
|
* Replaces the legacy `@atlaskit/popup` + `@atlaskit/portal` + `@atlaskit/layering` pipeline
|
|
83
|
-
* with native Popover API via `@atlaskit/top-layer`.
|
|
87
|
+
* with the native Popover API via `@atlaskit/top-layer`.
|
|
84
88
|
*
|
|
85
|
-
* What
|
|
89
|
+
* What is no longer needed:
|
|
86
90
|
* - Portal: top layer handles stacking natively
|
|
87
91
|
* - FocusLock / react-focus-lock: popover=auto provides light dismiss
|
|
88
92
|
* - z-index: top layer is always above everything
|
|
@@ -92,7 +96,7 @@ function LoadingIndicator(_ref) {
|
|
|
92
96
|
* - Fallback placements / Popper: CSS Anchor Positioning handles positioning
|
|
93
97
|
*/
|
|
94
98
|
function DropdownMenuTopLayer(_ref2) {
|
|
95
|
-
var
|
|
99
|
+
var _ref5;
|
|
96
100
|
var _ref2$autoFocus = _ref2.autoFocus,
|
|
97
101
|
autoFocus = _ref2$autoFocus === void 0 ? false : _ref2$autoFocus,
|
|
98
102
|
children = _ref2.children,
|
|
@@ -122,26 +126,38 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
122
126
|
isLocalOpen = _useControlledState2[0],
|
|
123
127
|
setLocalIsOpen = _useControlledState2[1];
|
|
124
128
|
var triggerRef = useRef(null);
|
|
129
|
+
var popoverRef = useRef(null);
|
|
125
130
|
var menuRef = useRef(null);
|
|
126
131
|
var _useState = useState(false),
|
|
127
132
|
_useState2 = _slicedToArray(_useState, 2),
|
|
128
133
|
isTriggeredUsingKeyboard = _useState2[0],
|
|
129
134
|
setTriggeredUsingKeyboard = _useState2[1];
|
|
135
|
+
var popoverId = usePopoverId();
|
|
130
136
|
var topLayerPlacement = useMemo(function () {
|
|
131
137
|
return fromLegacyPlacement({
|
|
132
138
|
legacy: placement
|
|
133
139
|
});
|
|
134
140
|
}, [placement]);
|
|
141
|
+
useAnchorPosition({
|
|
142
|
+
anchorRef: triggerRef,
|
|
143
|
+
popoverRef: popoverRef,
|
|
144
|
+
placement: topLayerPlacement
|
|
145
|
+
});
|
|
146
|
+
useWidthFromAnchor({
|
|
147
|
+
mode: shouldFitContainer ? 'min-anchor' : 'none',
|
|
148
|
+
popoverRef: popoverRef,
|
|
149
|
+
anchorRef: triggerRef
|
|
150
|
+
});
|
|
135
151
|
|
|
136
|
-
//
|
|
152
|
+
// Close handling.
|
|
137
153
|
// Focus restoration is handled natively by the Popover API:
|
|
138
|
-
// - Escape
|
|
139
|
-
// - Click-outside
|
|
154
|
+
// - Escape: browser restores focus to the trigger automatically
|
|
155
|
+
// - Click-outside: browser does NOT restore (correct behavior)
|
|
140
156
|
//
|
|
141
157
|
// The only custom focus handling needed is `returnFocusRef`: when provided,
|
|
142
158
|
// we redirect focus to a different element than the trigger. We do this
|
|
143
159
|
// in the onClose callback via rAF, which runs after the browser's native
|
|
144
|
-
// restoration
|
|
160
|
+
// restoration, effectively overriding it.
|
|
145
161
|
var handleOnClose = useCallback(function (_ref3) {
|
|
146
162
|
var _reason = _ref3.reason;
|
|
147
163
|
if (returnFocusRef) {
|
|
@@ -157,7 +173,7 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
157
173
|
});
|
|
158
174
|
}, [onOpenChange, returnFocusRef, setLocalIsOpen]);
|
|
159
175
|
|
|
160
|
-
//
|
|
176
|
+
// Trigger click handling.
|
|
161
177
|
var handleTriggerClicked = useCallback(function (event) {
|
|
162
178
|
var newValue = !isLocalOpen;
|
|
163
179
|
setTriggeredUsingKeyboard(isKeyboardTriggered(event));
|
|
@@ -184,7 +200,7 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
184
200
|
return noop;
|
|
185
201
|
}
|
|
186
202
|
|
|
187
|
-
//
|
|
203
|
+
// Do not open on ArrowDown if this trigger is inside a parent menu.
|
|
188
204
|
// Nested menus should only be opened via ArrowRight or Enter.
|
|
189
205
|
var isNestedTrigger = ((_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 ? void 0 : _triggerRef$current.closest('[role="menu"]')) != null;
|
|
190
206
|
return bind(window, {
|
|
@@ -200,7 +216,7 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
200
216
|
});
|
|
201
217
|
}, [isFocused, isLocalOpen, handleTriggerClicked]);
|
|
202
218
|
|
|
203
|
-
//
|
|
219
|
+
// Arrow navigation.
|
|
204
220
|
// useArrowNavigation handles ArrowUp/Down, Home/End, and Tab-to-close
|
|
205
221
|
// by querying focusable elements in the menu DOM container.
|
|
206
222
|
var handleArrowClose = useCallback(function () {
|
|
@@ -226,7 +242,7 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
226
242
|
filter: isAtCurrentMenuLevel
|
|
227
243
|
});
|
|
228
244
|
|
|
229
|
-
//
|
|
245
|
+
// Auto-focus first item on open.
|
|
230
246
|
useEffect(function () {
|
|
231
247
|
if (!isLocalOpen || !isTriggeredUsingKeyboard && !autoFocus) {
|
|
232
248
|
return;
|
|
@@ -243,7 +259,7 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
243
259
|
});
|
|
244
260
|
}, [isLocalOpen, isTriggeredUsingKeyboard, autoFocus]);
|
|
245
261
|
|
|
246
|
-
//
|
|
262
|
+
// Close on menu item click.
|
|
247
263
|
// Close when a regular menuitem is clicked, but not checkboxes/radios
|
|
248
264
|
// and not nested triggers (items with aria-haspopup).
|
|
249
265
|
var handleMenuClick = useCallback(function (e) {
|
|
@@ -254,7 +270,7 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
254
270
|
return;
|
|
255
271
|
}
|
|
256
272
|
var isCheckboxOrRadio = menuItem.getAttribute('role') === 'menuitemcheckbox' || menuItem.getAttribute('role') === 'menuitemradio';
|
|
257
|
-
//
|
|
273
|
+
// Do not close the menu when clicking a nested trigger (aria-haspopup).
|
|
258
274
|
// The nested dropdown will handle its own open/close.
|
|
259
275
|
var isNestedTrigger = menuItem.hasAttribute('aria-haspopup');
|
|
260
276
|
if (!isCheckboxOrRadio && !isNestedTrigger) {
|
|
@@ -265,27 +281,24 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
265
281
|
});
|
|
266
282
|
}
|
|
267
283
|
}, [setLocalIsOpen, onOpenChange]);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
_toggle = _ref5.toggle,
|
|
274
|
-
ariaAttributes = _ref5.ariaAttributes;
|
|
275
|
-
var combinedRef = mergeRefs([ref, triggerRef]);
|
|
284
|
+
var ariaAttributes = getAriaForTrigger({
|
|
285
|
+
role: 'menu',
|
|
286
|
+
isOpen: isLocalOpen,
|
|
287
|
+
popoverId: popoverId
|
|
288
|
+
});
|
|
276
289
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
var
|
|
290
|
+
// FUDGE(top-layer-api): cast `aria-haspopup` to the narrow shape that adopter
|
|
291
|
+
// public types expect. `@atlaskit/top-layer` types `aria-haspopup` as the wider
|
|
292
|
+
// WAI-ARIA union, but the public `CustomTriggerProps` (extending `@atlaskit/popup`
|
|
293
|
+
// `TriggerProps`) is intentionally kept narrow (`boolean | 'dialog'`) because the
|
|
294
|
+
// top-layer API surface is not yet settled. The runtime value is unchanged; only
|
|
295
|
+
// the TypeScript-visible type is narrowed at this boundary.
|
|
296
|
+
var narrowAriaAttributes = ariaAttributes;
|
|
297
|
+
var renderTrigger = function renderTrigger() {
|
|
298
|
+
var setRef = function setRef(node) {
|
|
299
|
+
triggerRef.current = node;
|
|
300
|
+
};
|
|
301
|
+
var combinedRef = mergeRefs([setRef]);
|
|
289
302
|
if (typeof trigger === 'function') {
|
|
290
303
|
return trigger(_objectSpread(_objectSpread(_objectSpread({}, narrowAriaAttributes), bindFocus), {}, {
|
|
291
304
|
triggerRef: combinedRef,
|
|
@@ -308,12 +321,16 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
308
321
|
"aria-label": label,
|
|
309
322
|
interactionName: interactionName
|
|
310
323
|
}), trigger);
|
|
311
|
-
}
|
|
324
|
+
};
|
|
325
|
+
return /*#__PURE__*/React.createElement(SelectionStore, null, renderTrigger(), /*#__PURE__*/React.createElement(Popover, {
|
|
326
|
+
ref: popoverRef,
|
|
327
|
+
id: popoverId,
|
|
312
328
|
role: "menu",
|
|
313
|
-
label: (
|
|
329
|
+
label: (_ref5 = menuLabel !== null && menuLabel !== void 0 ? menuLabel : label) !== null && _ref5 !== void 0 ? _ref5 : typeof trigger === 'string' ? trigger : 'Menu',
|
|
314
330
|
isOpen: isLocalOpen,
|
|
331
|
+
onClose: handleOnClose,
|
|
315
332
|
animate: animation,
|
|
316
|
-
|
|
333
|
+
placement: topLayerPlacement,
|
|
317
334
|
testId: testId && "".concat(testId, "--content")
|
|
318
335
|
}, /*#__PURE__*/React.createElement("div", {
|
|
319
336
|
ref: menuRef,
|
|
@@ -330,6 +347,6 @@ function DropdownMenuTopLayer(_ref2) {
|
|
|
330
347
|
}, isLoading ? /*#__PURE__*/React.createElement(LoadingIndicator, {
|
|
331
348
|
statusLabel: statusLabel,
|
|
332
349
|
testId: testId && "".concat(testId, "--menu-wrapper--loading-indicator")
|
|
333
|
-
}) : children))))
|
|
350
|
+
}) : children))));
|
|
334
351
|
}
|
|
335
352
|
export default DropdownMenuTopLayer;
|
|
@@ -3,9 +3,9 @@ import type { DropdownMenuProps } from './types';
|
|
|
3
3
|
* Top-layer implementation of DropdownMenu.
|
|
4
4
|
*
|
|
5
5
|
* Replaces the legacy `@atlaskit/popup` + `@atlaskit/portal` + `@atlaskit/layering` pipeline
|
|
6
|
-
* with native Popover API via `@atlaskit/top-layer`.
|
|
6
|
+
* with the native Popover API via `@atlaskit/top-layer`.
|
|
7
7
|
*
|
|
8
|
-
* What
|
|
8
|
+
* What is no longer needed:
|
|
9
9
|
* - Portal: top layer handles stacking natively
|
|
10
10
|
* - FocusLock / react-focus-lock: popover=auto provides light dismiss
|
|
11
11
|
* - z-index: top layer is always above everything
|
|
@@ -3,9 +3,9 @@ import type { DropdownMenuProps } from './types';
|
|
|
3
3
|
* Top-layer implementation of DropdownMenu.
|
|
4
4
|
*
|
|
5
5
|
* Replaces the legacy `@atlaskit/popup` + `@atlaskit/portal` + `@atlaskit/layering` pipeline
|
|
6
|
-
* with native Popover API via `@atlaskit/top-layer`.
|
|
6
|
+
* with the native Popover API via `@atlaskit/top-layer`.
|
|
7
7
|
*
|
|
8
|
-
* What
|
|
8
|
+
* What is no longer needed:
|
|
9
9
|
* - Portal: top layer handles stacking natively
|
|
10
10
|
* - FocusLock / react-focus-lock: popover=auto provides light dismiss
|
|
11
11
|
* - z-index: top layer is always above everything
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/dropdown-menu",
|
|
3
|
-
"version": "16.10.
|
|
3
|
+
"version": "16.10.2",
|
|
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/"
|
|
@@ -34,16 +34,16 @@
|
|
|
34
34
|
"@atlaskit/button": "^23.11.0",
|
|
35
35
|
"@atlaskit/css": "^0.19.0",
|
|
36
36
|
"@atlaskit/ds-lib": "^7.0.0",
|
|
37
|
-
"@atlaskit/icon": "^35.
|
|
38
|
-
"@atlaskit/layering": "^3.
|
|
37
|
+
"@atlaskit/icon": "^35.4.0",
|
|
38
|
+
"@atlaskit/layering": "^3.8.0",
|
|
39
39
|
"@atlaskit/menu": "^8.5.0",
|
|
40
40
|
"@atlaskit/platform-feature-flags": "^1.1.0",
|
|
41
41
|
"@atlaskit/popup": "^4.23.0",
|
|
42
42
|
"@atlaskit/primitives": "^19.0.0",
|
|
43
43
|
"@atlaskit/spinner": "^19.1.0",
|
|
44
44
|
"@atlaskit/theme": "^25.0.0",
|
|
45
|
-
"@atlaskit/tokens": "^13.
|
|
46
|
-
"@atlaskit/top-layer": "^0.
|
|
45
|
+
"@atlaskit/tokens": "^13.3.0",
|
|
46
|
+
"@atlaskit/top-layer": "^0.14.0",
|
|
47
47
|
"@atlaskit/visually-hidden": "^3.1.0",
|
|
48
48
|
"@babel/runtime": "^7.0.0",
|
|
49
49
|
"@compiled/react": "^0.20.0",
|