@atlaskit/dropdown-menu 16.10.1 → 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 CHANGED
@@ -1,5 +1,13 @@
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
+
3
11
  ## 16.10.1
4
12
 
5
13
  ### Patch 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 _popup = require("@atlaskit/top-layer/popup");
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>` from the trigger's `onClick` handler
46
- * - `React.KeyboardEvent<Element>` from the trigger's `onClick` when activated via keyboard
47
- * - `KeyboardEvent` native event from the ArrowDown `bind(window, ...)` listener
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's no longer needed:
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 _ref6;
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
- // ── Close handling ──
161
+ // Close handling.
146
162
  // Focus restoration is handled natively by the Popover API:
147
- // - Escape browser restores focus to the trigger automatically
148
- // - Click-outside browser does NOT restore (correct behavior)
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 effectively overriding it.
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
- // ── Trigger click handling ──
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
- // Don't open on ArrowDown if this trigger is inside a parent menu.
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
- // ── Arrow navigation ──
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
- // ── Auto-focus first item on open ──
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
- // ── Close on menu item click ──
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
- // Don't close the menu when clicking a nested trigger (aria-haspopup).
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
- return /*#__PURE__*/React.createElement(_selectionStore.default, null, /*#__PURE__*/React.createElement(_popup.Popup, {
278
- placement: topLayerPlacement,
279
- onClose: handleOnClose
280
- }, /*#__PURE__*/React.createElement(_popup.Popup.TriggerFunction, null, function (_ref5) {
281
- var ref = _ref5.ref,
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
- // FUDGE(top-layer-api): cast `ariaAttributes` to the narrow shape that adopter
287
- // public types expect. `@atlaskit/top-layer` types `aria-haspopup` as the wider
288
- // WAI-ARIA union (boolean | 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid'),
289
- // but the public `CustomTriggerProps` (extending `@atlaskit/popup` `TriggerProps`)
290
- // is intentionally kept narrow (boolean | 'dialog') because the top-layer API
291
- // surface is not yet settled. The runtime value is unchanged; only the
292
- // TypeScript-visible type is narrowed at this boundary.
293
- // REMOVE WHEN: the top-layer public API is committed (see
294
- // packages/design-system/top-layer/notes/decisions/migration-roadmap.md "Open API
295
- // decisions deferred to a follow-up PR") and a follow-up `minor` PR widens
296
- // `TriggerProps['aria-haspopup']` on `@atlaskit/popup` to match.
297
- var narrowAriaAttributes = ariaAttributes;
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
- }), /*#__PURE__*/React.createElement(_popup.Popup.Content, {
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: (_ref6 = menuLabel !== null && menuLabel !== void 0 ? menuLabel : label) !== null && _ref6 !== void 0 ? _ref6 : typeof trigger === 'string' ? trigger : 'Menu',
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
- widthFromAnchor: shouldFitContainer ? 'min-anchor' : 'none',
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 { Popup } from '@atlaskit/top-layer/popup';
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>` from the trigger's `onClick` handler
33
- * - `React.KeyboardEvent<Element>` from the trigger's `onClick` when activated via keyboard
34
- * - `KeyboardEvent` native event from the ArrowDown `bind(window, ...)` listener
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's no longer needed:
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
- // ── Close handling ──
133
+ // Close handling.
118
134
  // Focus restoration is handled natively by the Popover API:
119
- // - Escape browser restores focus to the trigger automatically
120
- // - Click-outside browser does NOT restore (correct behavior)
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 effectively overriding it.
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
- // ── Trigger click handling ──
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
- // Don't open on ArrowDown if this trigger is inside a parent menu.
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
- // ── Arrow navigation ──
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
- // ── Auto-focus first item on open ──
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
- // ── Close on menu item click ──
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
- // Don't close the menu when clicking a nested trigger (aria-haspopup).
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
- return /*#__PURE__*/React.createElement(SelectionStore, null, /*#__PURE__*/React.createElement(Popup, {
253
- placement: topLayerPlacement,
254
- onClose: handleOnClose
255
- }, /*#__PURE__*/React.createElement(Popup.TriggerFunction, null, ({
256
- ref,
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
- // FUDGE(top-layer-api): cast `ariaAttributes` to the narrow shape that adopter
263
- // public types expect. `@atlaskit/top-layer` types `aria-haspopup` as the wider
264
- // WAI-ARIA union (boolean | 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid'),
265
- // but the public `CustomTriggerProps` (extending `@atlaskit/popup` `TriggerProps`)
266
- // is intentionally kept narrow (boolean | 'dialog') because the top-layer API
267
- // surface is not yet settled. The runtime value is unchanged; only the
268
- // TypeScript-visible type is narrowed at this boundary.
269
- // REMOVE WHEN: the top-layer public API is committed (see
270
- // packages/design-system/top-layer/notes/decisions/migration-roadmap.md "Open API
271
- // decisions deferred to a follow-up PR") and a follow-up `minor` PR widens
272
- // `TriggerProps['aria-haspopup']` on `@atlaskit/popup` to match.
273
- const narrowAriaAttributes = ariaAttributes;
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
- }), /*#__PURE__*/React.createElement(Popup.Content, {
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
- widthFromAnchor: shouldFitContainer ? 'min-anchor' : 'none',
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 { Popup } from '@atlaskit/top-layer/popup';
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>` 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
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's no longer needed:
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 _ref6;
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
- // ── Close handling ──
152
+ // Close handling.
137
153
  // Focus restoration is handled natively by the Popover API:
138
- // - Escape browser restores focus to the trigger automatically
139
- // - Click-outside browser does NOT restore (correct behavior)
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 effectively overriding it.
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
- // ── Trigger click handling ──
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
- // Don't open on ArrowDown if this trigger is inside a parent menu.
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
- // ── Arrow navigation ──
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
- // ── Auto-focus first item on open ──
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
- // ── Close on menu item click ──
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
- // Don't close the menu when clicking a nested trigger (aria-haspopup).
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
- return /*#__PURE__*/React.createElement(SelectionStore, null, /*#__PURE__*/React.createElement(Popup, {
269
- placement: topLayerPlacement,
270
- onClose: handleOnClose
271
- }, /*#__PURE__*/React.createElement(Popup.TriggerFunction, null, function (_ref5) {
272
- var ref = _ref5.ref,
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
- // FUDGE(top-layer-api): cast `ariaAttributes` to the narrow shape that adopter
278
- // public types expect. `@atlaskit/top-layer` types `aria-haspopup` as the wider
279
- // WAI-ARIA union (boolean | 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid'),
280
- // but the public `CustomTriggerProps` (extending `@atlaskit/popup` `TriggerProps`)
281
- // is intentionally kept narrow (boolean | 'dialog') because the top-layer API
282
- // surface is not yet settled. The runtime value is unchanged; only the
283
- // TypeScript-visible type is narrowed at this boundary.
284
- // REMOVE WHEN: the top-layer public API is committed (see
285
- // packages/design-system/top-layer/notes/decisions/migration-roadmap.md "Open API
286
- // decisions deferred to a follow-up PR") and a follow-up `minor` PR widens
287
- // `TriggerProps['aria-haspopup']` on `@atlaskit/popup` to match.
288
- var narrowAriaAttributes = ariaAttributes;
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
- }), /*#__PURE__*/React.createElement(Popup.Content, {
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: (_ref6 = menuLabel !== null && menuLabel !== void 0 ? menuLabel : label) !== null && _ref6 !== void 0 ? _ref6 : typeof trigger === 'string' ? trigger : 'Menu',
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
- widthFromAnchor: shouldFitContainer ? 'min-anchor' : 'none',
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's no longer needed:
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's no longer needed:
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.1",
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/"
@@ -35,15 +35,15 @@
35
35
  "@atlaskit/css": "^0.19.0",
36
36
  "@atlaskit/ds-lib": "^7.0.0",
37
37
  "@atlaskit/icon": "^35.4.0",
38
- "@atlaskit/layering": "^3.7.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.1.0",
46
- "@atlaskit/top-layer": "^0.13.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",