@atlaskit/dropdown-menu 16.8.9 → 16.8.11

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +203 -0
  2. package/dist/cjs/dropdown-menu-top-layer.compiled.css +10 -0
  3. package/dist/cjs/dropdown-menu-top-layer.js +347 -0
  4. package/dist/cjs/dropdown-menu.js +62 -8
  5. package/dist/cjs/internal/components/group-title.js +6 -1
  6. package/dist/cjs/internal/use-arrow-navigation/index.js +25 -0
  7. package/dist/cjs/internal/use-arrow-navigation/use-arrow-navigation.js +18 -0
  8. package/dist/es2019/dropdown-menu-top-layer.compiled.css +10 -0
  9. package/dist/es2019/dropdown-menu-top-layer.js +323 -0
  10. package/dist/es2019/dropdown-menu.js +59 -9
  11. package/dist/es2019/internal/components/group-title.js +6 -1
  12. package/dist/es2019/internal/use-arrow-navigation/index.js +2 -0
  13. package/dist/es2019/internal/use-arrow-navigation/use-arrow-navigation.js +1 -0
  14. package/dist/esm/dropdown-menu-top-layer.compiled.css +10 -0
  15. package/dist/esm/dropdown-menu-top-layer.js +338 -0
  16. package/dist/esm/dropdown-menu.js +62 -8
  17. package/dist/esm/internal/components/group-title.js +6 -1
  18. package/dist/esm/internal/use-arrow-navigation/index.js +2 -0
  19. package/dist/esm/internal/use-arrow-navigation/use-arrow-navigation.js +1 -0
  20. package/dist/types/dropdown-menu-top-layer.d.ts +18 -0
  21. package/dist/types/dropdown-menu.d.ts +1 -1
  22. package/dist/types/internal/components/group-title.d.ts +6 -1
  23. package/dist/types/internal/use-arrow-navigation/index.d.ts +2 -0
  24. package/dist/types/internal/use-arrow-navigation/use-arrow-navigation.d.ts +1 -0
  25. package/dist/types/types.d.ts +20 -0
  26. package/dist/types-ts4.5/dropdown-menu-top-layer.d.ts +18 -0
  27. package/dist/types-ts4.5/dropdown-menu.d.ts +1 -1
  28. package/dist/types-ts4.5/internal/components/group-title.d.ts +6 -1
  29. package/dist/types-ts4.5/internal/use-arrow-navigation/index.d.ts +2 -0
  30. package/dist/types-ts4.5/internal/use-arrow-navigation/use-arrow-navigation.d.ts +1 -0
  31. package/dist/types-ts4.5/types.d.ts +20 -0
  32. package/package.json +21 -16
  33. package/offerings.json +0 -36
@@ -0,0 +1,338 @@
1
+ /* dropdown-menu-top-layer.tsx generated by @compiled/babel-plugin v0.39.1 */
2
+ import _extends from "@babel/runtime/helpers/extends";
3
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
5
+ import "./dropdown-menu-top-layer.compiled.css";
6
+ import * as React from 'react';
7
+ import { ax, ix } from "@compiled/react/runtime";
8
+ 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; }
9
+ 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; }
10
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
11
+ import { bind } from 'bind-event-listener';
12
+ import Button from '@atlaskit/button/new';
13
+ import { KEY_DOWN, KEY_ENTER, KEY_SPACE } from '@atlaskit/ds-lib/keycodes';
14
+ import mergeRefs from '@atlaskit/ds-lib/merge-refs';
15
+ import noop from '@atlaskit/ds-lib/noop';
16
+ import useControlledState from '@atlaskit/ds-lib/use-controlled';
17
+ import useFocus from '@atlaskit/ds-lib/use-focus-event';
18
+ import ExpandIcon from '@atlaskit/icon/core/chevron-down';
19
+ import MenuGroup from '@atlaskit/menu/menu-group';
20
+ import Spinner from '@atlaskit/spinner';
21
+ import { slideAndFade } from '@atlaskit/top-layer/animations';
22
+ import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map';
23
+ import { Popup } from '@atlaskit/top-layer/popup';
24
+ import SelectionStore from './internal/context/selection-store';
25
+ import { getFirstFocusable, isAtCurrentMenuLevel, useArrowNavigation } from './internal/use-arrow-navigation';
26
+ var MAX_HEIGHT = "calc(100vh - 16px)";
27
+ var styles = {
28
+ spinnerContainer: "_1e0c1txw _1ul91lit _1bah1h6o _1q51v47k _y4tiv47k _85i5v47k _bozgv47k",
29
+ menuContent: "_2rkofajl _bfhk1bhr _16qs130s"
30
+ };
31
+ var animation = slideAndFade();
32
+
33
+ /**
34
+ * Event types produced by trigger interactions.
35
+ *
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
39
+ */
40
+
41
+ /**
42
+ * Determines whether a trigger interaction was keyboard-initiated.
43
+ *
44
+ * Keyboard signals:
45
+ * - `type === 'keydown'` (native KeyboardEvent from ArrowDown handler)
46
+ * - `clientX/clientY === 0` (assistive technology click)
47
+ * - `detail === 0` (keyboard-activated click via Enter/Space)
48
+ */
49
+ function isKeyboardTriggered(event) {
50
+ if (event.type === 'keydown') {
51
+ return true;
52
+ }
53
+ if ('clientX' in event && (event.clientX === 0 || event.clientY === 0)) {
54
+ return true;
55
+ }
56
+ if (event.detail === 0) {
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+
62
+ /**
63
+ * Loading indicator for the dropdown menu.
64
+ */
65
+ function LoadingIndicator(_ref) {
66
+ var _ref$statusLabel = _ref.statusLabel,
67
+ statusLabel = _ref$statusLabel === void 0 ? 'Loading' : _ref$statusLabel,
68
+ testId = _ref.testId;
69
+ return /*#__PURE__*/React.createElement("div", {
70
+ role: "menuitem",
71
+ className: ax([styles.spinnerContainer])
72
+ }, /*#__PURE__*/React.createElement(Spinner, {
73
+ size: "small",
74
+ label: statusLabel,
75
+ testId: testId
76
+ }));
77
+ }
78
+
79
+ /**
80
+ * Top-layer implementation of DropdownMenu.
81
+ *
82
+ * Replaces the legacy `@atlaskit/popup` + `@atlaskit/portal` + `@atlaskit/layering` pipeline
83
+ * with native Popover API via `@atlaskit/top-layer`.
84
+ *
85
+ * What's no longer needed:
86
+ * - Portal: top layer handles stacking natively
87
+ * - FocusLock / react-focus-lock: popover=auto provides light dismiss
88
+ * - z-index: top layer is always above everything
89
+ * - FocusManager (ref registration): replaced by DOM-query-based `useArrowNavigation`
90
+ * - handle-focus.tsx: replaced by `useArrowNavigation`
91
+ * - Layering context: top layer nesting is handled by the browser
92
+ * - Fallback placements / Popper: CSS Anchor Positioning handles positioning
93
+ */
94
+ function DropdownMenuTopLayer(_ref2) {
95
+ var _ref6;
96
+ var _ref2$autoFocus = _ref2.autoFocus,
97
+ autoFocus = _ref2$autoFocus === void 0 ? false : _ref2$autoFocus,
98
+ children = _ref2.children,
99
+ _ref2$defaultOpen = _ref2.defaultOpen,
100
+ defaultOpen = _ref2$defaultOpen === void 0 ? false : _ref2$defaultOpen,
101
+ _ref2$isLoading = _ref2.isLoading,
102
+ isLoading = _ref2$isLoading === void 0 ? false : _ref2$isLoading,
103
+ isOpenProp = _ref2.isOpen,
104
+ _ref2$onOpenChange = _ref2.onOpenChange,
105
+ onOpenChange = _ref2$onOpenChange === void 0 ? noop : _ref2$onOpenChange,
106
+ _ref2$placement = _ref2.placement,
107
+ placement = _ref2$placement === void 0 ? 'bottom-start' : _ref2$placement,
108
+ _ref2$shouldFitContai = _ref2.shouldFitContainer,
109
+ shouldFitContainer = _ref2$shouldFitContai === void 0 ? false : _ref2$shouldFitContai,
110
+ returnFocusRef = _ref2.returnFocusRef,
111
+ spacing = _ref2.spacing,
112
+ statusLabel = _ref2.statusLabel,
113
+ testId = _ref2.testId,
114
+ trigger = _ref2.trigger,
115
+ label = _ref2.label,
116
+ interactionName = _ref2.interactionName,
117
+ menuLabel = _ref2.menuLabel;
118
+ var _useControlledState = useControlledState(isOpenProp, function () {
119
+ return defaultOpen;
120
+ }),
121
+ _useControlledState2 = _slicedToArray(_useControlledState, 2),
122
+ isLocalOpen = _useControlledState2[0],
123
+ setLocalIsOpen = _useControlledState2[1];
124
+ var triggerRef = useRef(null);
125
+ var menuRef = useRef(null);
126
+ var _useState = useState(false),
127
+ _useState2 = _slicedToArray(_useState, 2),
128
+ isTriggeredUsingKeyboard = _useState2[0],
129
+ setTriggeredUsingKeyboard = _useState2[1];
130
+ var topLayerPlacement = useMemo(function () {
131
+ return fromLegacyPlacement({
132
+ legacy: placement
133
+ });
134
+ }, [placement]);
135
+
136
+ // ── Close handling ──
137
+ // 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)
140
+ //
141
+ // The only custom focus handling needed is `returnFocusRef`: when provided,
142
+ // we redirect focus to a different element than the trigger. We do this
143
+ // in the onClose callback via rAF, which runs after the browser's native
144
+ // restoration — effectively overriding it.
145
+ var handleOnClose = useCallback(function (_ref3) {
146
+ var _reason = _ref3.reason;
147
+ if (returnFocusRef) {
148
+ requestAnimationFrame(function () {
149
+ var _returnFocusRef$curre;
150
+ (_returnFocusRef$curre = returnFocusRef.current) === null || _returnFocusRef$curre === void 0 || _returnFocusRef$curre.focus();
151
+ });
152
+ }
153
+ setLocalIsOpen(false);
154
+ onOpenChange({
155
+ isOpen: false,
156
+ event: null
157
+ });
158
+ }, [onOpenChange, returnFocusRef, setLocalIsOpen]);
159
+
160
+ // ── Trigger click handling ──
161
+ var handleTriggerClicked = useCallback(function (event) {
162
+ var newValue = !isLocalOpen;
163
+ setTriggeredUsingKeyboard(isKeyboardTriggered(event));
164
+ setLocalIsOpen(newValue);
165
+
166
+ // Extract the native DOM event for onOpenChange
167
+ var nativeEvent = 'nativeEvent' in event ? event.nativeEvent : event;
168
+ onOpenChange({
169
+ isOpen: newValue,
170
+ event: nativeEvent
171
+ });
172
+ }, [isLocalOpen, setLocalIsOpen, onOpenChange]);
173
+ var _useFocus = useFocus(),
174
+ isFocused = _useFocus.isFocused,
175
+ bindFocus = _useFocus.bindFocus;
176
+
177
+ // When trigger is focused, open dropdown on ArrowDown (top-level only).
178
+ // Per WAI-ARIA, ArrowDown opens a menu from a menubar/button trigger,
179
+ // but inside a vertical submenu, ArrowDown navigates between siblings
180
+ // and ArrowRight opens nested menus instead.
181
+ useEffect(function () {
182
+ var _triggerRef$current;
183
+ if (!isFocused || isLocalOpen) {
184
+ return noop;
185
+ }
186
+
187
+ // Don't open on ArrowDown if this trigger is inside a parent menu.
188
+ // Nested menus should only be opened via ArrowRight or Enter.
189
+ var isNestedTrigger = ((_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 ? void 0 : _triggerRef$current.closest('[role="menu"]')) != null;
190
+ return bind(window, {
191
+ type: 'keydown',
192
+ listener: function openOnKeyDown(e) {
193
+ if (e.key === KEY_DOWN && !isNestedTrigger) {
194
+ e.preventDefault();
195
+ handleTriggerClicked(e);
196
+ } else if ((e.code === KEY_SPACE || e.key === KEY_ENTER) && e.detail === 0) {
197
+ setTriggeredUsingKeyboard(true);
198
+ }
199
+ }
200
+ });
201
+ }, [isFocused, isLocalOpen, handleTriggerClicked]);
202
+
203
+ // ── Arrow navigation ──
204
+ // useArrowNavigation handles ArrowUp/Down, Home/End, and Tab-to-close
205
+ // by querying focusable elements in the menu DOM container.
206
+ var handleArrowClose = useCallback(function () {
207
+ handleOnClose({
208
+ reason: 'escape'
209
+ });
210
+ }, [handleOnClose]);
211
+ var handleNestedOpen = useCallback(function (_ref4) {
212
+ var trigger = _ref4.trigger;
213
+ trigger.click();
214
+ }, []);
215
+ var handleNestedClose = useCallback(function () {
216
+ handleOnClose({
217
+ reason: 'escape'
218
+ });
219
+ }, [handleOnClose]);
220
+ useArrowNavigation({
221
+ containerRef: menuRef,
222
+ onClose: handleArrowClose,
223
+ onNestedOpen: handleNestedOpen,
224
+ onNestedClose: handleNestedClose,
225
+ isEnabled: isLocalOpen,
226
+ filter: isAtCurrentMenuLevel
227
+ });
228
+
229
+ // ── Auto-focus first item on open ──
230
+ useEffect(function () {
231
+ if (!isLocalOpen || !isTriggeredUsingKeyboard && !autoFocus) {
232
+ return;
233
+ }
234
+ requestAnimationFrame(function () {
235
+ var menu = menuRef.current;
236
+ if (!menu) {
237
+ return;
238
+ }
239
+ var firstItem = getFirstFocusable({
240
+ container: menu
241
+ });
242
+ firstItem === null || firstItem === void 0 || firstItem.focus();
243
+ });
244
+ }, [isLocalOpen, isTriggeredUsingKeyboard, autoFocus]);
245
+
246
+ // shouldFitContainer is handled by the width prop on Popup.Content below.
247
+ var popupContentWidth = shouldFitContainer ? 'min-trigger' : 'content';
248
+
249
+ // ── Close on menu item click ──
250
+ // Close when a regular menuitem is clicked, but not checkboxes/radios
251
+ // and not nested triggers (items with aria-haspopup).
252
+ var handleMenuClick = useCallback(function (e) {
253
+ var _target$closest;
254
+ var target = e.target;
255
+ var menuItem = (_target$closest = target.closest) === null || _target$closest === void 0 ? void 0 : _target$closest.call(target, '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]');
256
+ if (!menuItem) {
257
+ return;
258
+ }
259
+ var isCheckboxOrRadio = menuItem.getAttribute('role') === 'menuitemcheckbox' || menuItem.getAttribute('role') === 'menuitemradio';
260
+ // Don't close the menu when clicking a nested trigger (aria-haspopup).
261
+ // The nested dropdown will handle its own open/close.
262
+ var isNestedTrigger = menuItem.hasAttribute('aria-haspopup');
263
+ if (!isCheckboxOrRadio && !isNestedTrigger) {
264
+ setLocalIsOpen(false);
265
+ onOpenChange({
266
+ isOpen: false,
267
+ event: e.nativeEvent
268
+ });
269
+ }
270
+ }, [setLocalIsOpen, onOpenChange]);
271
+ return /*#__PURE__*/React.createElement(SelectionStore, null, /*#__PURE__*/React.createElement(Popup, {
272
+ placement: topLayerPlacement,
273
+ onClose: handleOnClose
274
+ }, /*#__PURE__*/React.createElement(Popup.TriggerFunction, null, function (_ref5) {
275
+ var ref = _ref5.ref,
276
+ _toggle = _ref5.toggle,
277
+ ariaAttributes = _ref5.ariaAttributes;
278
+ var combinedRef = mergeRefs([ref, triggerRef]);
279
+
280
+ // FUDGE(top-layer-api): cast `ariaAttributes` to the narrow shape that adopter
281
+ // public types expect. `@atlaskit/top-layer` types `aria-haspopup` as the wider
282
+ // WAI-ARIA union (boolean | 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid'),
283
+ // but the public `CustomTriggerProps` (extending `@atlaskit/popup` `TriggerProps`)
284
+ // is intentionally kept narrow (boolean | 'dialog') because the top-layer API
285
+ // surface is not yet settled. The runtime value is unchanged; only the
286
+ // TypeScript-visible type is narrowed at this boundary.
287
+ // REMOVE WHEN: the top-layer public API is committed (see
288
+ // packages/design-system/top-layer/notes/decisions/migration-roadmap.md "Open API
289
+ // decisions deferred to a follow-up PR") and a follow-up `minor` PR widens
290
+ // `TriggerProps['aria-haspopup']` on `@atlaskit/popup` to match.
291
+ var narrowAriaAttributes = ariaAttributes;
292
+ if (typeof trigger === 'function') {
293
+ return trigger(_objectSpread(_objectSpread(_objectSpread({}, narrowAriaAttributes), bindFocus), {}, {
294
+ triggerRef: combinedRef,
295
+ isSelected: isLocalOpen,
296
+ onClick: handleTriggerClicked,
297
+ testId: testId && "".concat(testId, "--trigger")
298
+ }));
299
+ }
300
+ return /*#__PURE__*/React.createElement(Button, _extends({}, bindFocus, {
301
+ ref: combinedRef
302
+ }, narrowAriaAttributes, {
303
+ isSelected: isLocalOpen,
304
+ iconAfter: function iconAfter(iconProps) {
305
+ return /*#__PURE__*/React.createElement(ExpandIcon, _extends({}, iconProps, {
306
+ size: "small"
307
+ }));
308
+ },
309
+ onClick: handleTriggerClicked,
310
+ testId: testId && "".concat(testId, "--trigger"),
311
+ "aria-label": label,
312
+ interactionName: interactionName
313
+ }), trigger);
314
+ }), /*#__PURE__*/React.createElement(Popup.Content, {
315
+ role: "menu",
316
+ label: (_ref6 = menuLabel !== null && menuLabel !== void 0 ? menuLabel : label) !== null && _ref6 !== void 0 ? _ref6 : typeof trigger === 'string' ? trigger : 'Menu',
317
+ isOpen: isLocalOpen,
318
+ animate: animation,
319
+ width: popupContentWidth,
320
+ testId: testId && "".concat(testId, "--content")
321
+ }, /*#__PURE__*/React.createElement("div", {
322
+ ref: menuRef,
323
+ className: ax([styles.menuContent])
324
+ }, /*#__PURE__*/React.createElement(MenuGroup, {
325
+ isLoading: isLoading,
326
+ maxHeight: MAX_HEIGHT,
327
+ maxWidth: shouldFitContainer ? undefined : 800,
328
+ onClick: handleMenuClick,
329
+ role: "menu",
330
+ spacing: spacing,
331
+ testId: testId && "".concat(testId, "--menu-wrapper--menu-group"),
332
+ menuLabel: menuLabel
333
+ }, isLoading ? /*#__PURE__*/React.createElement(LoadingIndicator, {
334
+ statusLabel: statusLabel,
335
+ testId: testId && "".concat(testId, "--menu-wrapper--loading-indicator")
336
+ }) : children)))));
337
+ }
338
+ export default DropdownMenuTopLayer;
@@ -15,9 +15,11 @@ import useControlledState from '@atlaskit/ds-lib/use-controlled';
15
15
  import useFocus from '@atlaskit/ds-lib/use-focus-event';
16
16
  import ExpandIcon from '@atlaskit/icon/core/chevron-down';
17
17
  import { useLayering } from '@atlaskit/layering';
18
+ import { fg } from '@atlaskit/platform-feature-flags';
18
19
  import Popup from '@atlaskit/popup';
19
20
  // eslint-disable-next-line @atlaskit/design-system/no-deprecated-imports
20
21
  import { layers } from '@atlaskit/theme/constants';
22
+ import DropdownMenuTopLayer from './dropdown-menu-top-layer';
21
23
  import FocusManager from './internal/components/focus-manager';
22
24
  import MenuWrapper from './internal/components/menu-wrapper';
23
25
  import SelectionStore from './internal/context/selection-store';
@@ -56,15 +58,9 @@ function isKeyboardEvent(event) {
56
58
  }
57
59
 
58
60
  /**
59
- * __Dropdown menu__
60
- *
61
- * A dropdown menu displays a list of actions or options to a user.
62
- *
63
- * - [Examples](https://atlassian.design/components/dropdown-menu/examples)
64
- * - [Code](https://atlassian.design/components/dropdown-menu/code)
65
- * - [Usage](https://atlassian.design/components/dropdown-menu/usage)
61
+ * Legacy Popper/Popup implementation (hooks run unconditionally when this component mounts).
66
62
  */
67
- var DropdownMenu = function DropdownMenu(_ref) {
63
+ function DropdownMenuLegacy(_ref) {
68
64
  var _ref$autoFocus = _ref.autoFocus,
69
65
  autoFocus = _ref$autoFocus === void 0 ? false : _ref$autoFocus,
70
66
  children = _ref.children,
@@ -336,5 +332,63 @@ var DropdownMenu = function DropdownMenu(_ref) {
336
332
  }, children));
337
333
  }
338
334
  })));
335
+ }
336
+
337
+ /**
338
+ * __Dropdown menu__
339
+ *
340
+ * A dropdown menu displays a list of actions or options to a user.
341
+ *
342
+ * - [Examples](https://atlassian.design/components/dropdown-menu/examples)
343
+ * - [Code](https://atlassian.design/components/dropdown-menu/code)
344
+ * - [Usage](https://atlassian.design/components/dropdown-menu/usage)
345
+ */
346
+ var DropdownMenu = function DropdownMenu(props) {
347
+ var _props$autoFocus = props.autoFocus,
348
+ autoFocus = _props$autoFocus === void 0 ? false : _props$autoFocus,
349
+ children = props.children,
350
+ _props$defaultOpen = props.defaultOpen,
351
+ defaultOpen = _props$defaultOpen === void 0 ? false : _props$defaultOpen,
352
+ _props$isLoading = props.isLoading,
353
+ isLoading = _props$isLoading === void 0 ? false : _props$isLoading,
354
+ isOpen = props.isOpen,
355
+ _props$onOpenChange = props.onOpenChange,
356
+ onOpenChange = _props$onOpenChange === void 0 ? noop : _props$onOpenChange,
357
+ _props$placement = props.placement,
358
+ placement = _props$placement === void 0 ? 'bottom-start' : _props$placement,
359
+ _props$shouldFitConta = props.shouldFitContainer,
360
+ shouldFitContainer = _props$shouldFitConta === void 0 ? false : _props$shouldFitConta,
361
+ returnFocusRef = props.returnFocusRef,
362
+ spacing = props.spacing,
363
+ statusLabel = props.statusLabel,
364
+ testId = props.testId,
365
+ trigger = props.trigger,
366
+ label = props.label,
367
+ interactionName = props.interactionName,
368
+ menuLabel = props.menuLabel;
369
+ if (fg('platform-dst-top-layer')) {
370
+ return /*#__PURE__*/React.createElement(DropdownMenuTopLayer, {
371
+ autoFocus: autoFocus,
372
+ children: children,
373
+ defaultOpen: defaultOpen,
374
+ isLoading: isLoading,
375
+ isOpen: isOpen,
376
+ onOpenChange: onOpenChange,
377
+ placement: placement,
378
+ shouldFitContainer: shouldFitContainer,
379
+ returnFocusRef: returnFocusRef,
380
+ spacing: spacing,
381
+ statusLabel: statusLabel,
382
+ testId: testId,
383
+ trigger: trigger,
384
+ label: label,
385
+ interactionName: interactionName,
386
+ menuLabel: menuLabel
387
+ });
388
+ }
389
+
390
+ // Forward full public props to the legacy Popper/Popup implementation unchanged.
391
+ // eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props -- wrapper delegates entire DropdownMenuProps API
392
+ return /*#__PURE__*/React.createElement(DropdownMenuLegacy, props);
339
393
  };
340
394
  export default DropdownMenu;
@@ -10,7 +10,12 @@ var styles = {
10
10
  /**
11
11
  * __Group title__
12
12
  *
13
- * Used to visually represent the title for DropdownMenu groups
13
+ * Used to visually represent the title for DropdownMenu groups.
14
+ *
15
+ * Pre-existing a11y note: uses `role="menuitem"` with `aria-hidden="true"`.
16
+ * WAI-ARIA APG recommends `role="presentation"` for non-interactive group
17
+ * headings, with the group linked via `aria-labelledby`. Out of scope for
18
+ * the top-layer migration — this is legacy behavior.
14
19
  *
15
20
  * @internal
16
21
  */
@@ -0,0 +1,2 @@
1
+ export { useArrowNavigation, isAtCurrentMenuLevel } from './use-arrow-navigation';
2
+ export { getFirstFocusable } from '@atlaskit/top-layer/focus';
@@ -0,0 +1 @@
1
+ export { useArrowNavigation, isAtCurrentMenuLevel } from '@atlaskit/top-layer/use-arrow-navigation';
@@ -0,0 +1,18 @@
1
+ import type { DropdownMenuProps } from './types';
2
+ /**
3
+ * Top-layer implementation of DropdownMenu.
4
+ *
5
+ * Replaces the legacy `@atlaskit/popup` + `@atlaskit/portal` + `@atlaskit/layering` pipeline
6
+ * with native Popover API via `@atlaskit/top-layer`.
7
+ *
8
+ * What's no longer needed:
9
+ * - Portal: top layer handles stacking natively
10
+ * - FocusLock / react-focus-lock: popover=auto provides light dismiss
11
+ * - z-index: top layer is always above everything
12
+ * - FocusManager (ref registration): replaced by DOM-query-based `useArrowNavigation`
13
+ * - handle-focus.tsx: replaced by `useArrowNavigation`
14
+ * - Layering context: top layer nesting is handled by the browser
15
+ * - Fallback placements / Popper: CSS Anchor Positioning handles positioning
16
+ */
17
+ declare function DropdownMenuTopLayer({ autoFocus, children, defaultOpen, isLoading, isOpen: isOpenProp, onOpenChange, placement, shouldFitContainer, returnFocusRef, spacing, statusLabel, testId, trigger, label, interactionName, menuLabel, }: DropdownMenuProps): React.JSX.Element;
18
+ export default DropdownMenuTopLayer;
@@ -9,5 +9,5 @@ import type { DropdownMenuProps } from './types';
9
9
  * - [Code](https://atlassian.design/components/dropdown-menu/code)
10
10
  * - [Usage](https://atlassian.design/components/dropdown-menu/usage)
11
11
  */
12
- declare const DropdownMenu: <T extends HTMLElement = any>({ autoFocus, children, defaultOpen, isLoading, isOpen, onOpenChange, placement, shouldFitContainer, shouldFlip, shouldRenderToParent, returnFocusRef, spacing, statusLabel, testId, trigger, zIndex, label, interactionName, strategy, menuLabel, shouldPreventEscapePropagation, }: DropdownMenuProps<T>) => React.JSX.Element;
12
+ declare const DropdownMenu: <T extends HTMLElement = any>(props: DropdownMenuProps<T>) => React.JSX.Element;
13
13
  export default DropdownMenu;
@@ -2,7 +2,12 @@ import React from 'react';
2
2
  /**
3
3
  * __Group title__
4
4
  *
5
- * Used to visually represent the title for DropdownMenu groups
5
+ * Used to visually represent the title for DropdownMenu groups.
6
+ *
7
+ * Pre-existing a11y note: uses `role="menuitem"` with `aria-hidden="true"`.
8
+ * WAI-ARIA APG recommends `role="presentation"` for non-interactive group
9
+ * headings, with the group linked via `aria-labelledby`. Out of scope for
10
+ * the top-layer migration — this is legacy behavior.
6
11
  *
7
12
  * @internal
8
13
  */
@@ -0,0 +1,2 @@
1
+ export { useArrowNavigation, isAtCurrentMenuLevel, type TUseArrowNavigationArgs } from './use-arrow-navigation';
2
+ export { getFirstFocusable } from '@atlaskit/top-layer/focus';
@@ -0,0 +1 @@
1
+ export { useArrowNavigation, isAtCurrentMenuLevel, type TUseArrowNavigationArgs, } from '@atlaskit/top-layer/use-arrow-navigation';
@@ -114,6 +114,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
114
114
  /**
115
115
  * Allows the dropdown menu to be placed on the opposite side of its trigger if it does not
116
116
  * fit in the viewport.
117
+ *
118
+ * @private
119
+ * @deprecated No-op when `platform-dst-top-layer` is enabled — CSS Anchor Positioning
120
+ * handles flipping natively via `position-try-fallbacks`.
117
121
  */
118
122
  shouldFlip?: boolean;
119
123
  /**
@@ -121,6 +125,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
121
125
  * `true` renders the dropdown menu in the DOM node closest to the trigger; focus is not trapped inside the element.
122
126
  * `false` renders the dropdown menu in React.Portal and focus is trapped inside the element.
123
127
  * Defaults to `false`.
128
+ *
129
+ * @private
130
+ * @deprecated No-op when `platform-dst-top-layer` is enabled — content always
131
+ * renders in the browser's top layer.
124
132
  */
125
133
  shouldRenderToParent?: boolean;
126
134
  /**
@@ -162,6 +170,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
162
170
  * Z-index that the popup should be displayed in.
163
171
  * This is passed to the portal component.
164
172
  * Defaults to `layers.modal()` from `@atlaskit/theme` which is 510.
173
+ *
174
+ * @private
175
+ * @deprecated No-op when `platform-dst-top-layer` is enabled — the browser's
176
+ * top layer manages stacking without z-index.
165
177
  */
166
178
  zIndex?: number;
167
179
  /**
@@ -179,6 +191,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
179
191
  /**
180
192
  * This controls the positioning strategy to use. Can vary between `absolute` and `fixed`.
181
193
  * The default is `fixed`.
194
+ *
195
+ * @private
196
+ * @deprecated No-op when `platform-dst-top-layer` is enabled — CSS Anchor Positioning
197
+ * replaces Popper's positioning strategy.
182
198
  */
183
199
  strategy?: 'absolute' | 'fixed';
184
200
  /**
@@ -188,6 +204,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
188
204
  /**
189
205
  * When set to true, will call stopPropagation on the ESCAPE key event.
190
206
  * This prevents the ESCAPE event from bubbling up to parent elements.
207
+ *
208
+ * @private
209
+ * @deprecated No-op when `platform-dst-top-layer` is enabled — the native
210
+ * popover dismiss handles Escape propagation.
191
211
  */
192
212
  shouldPreventEscapePropagation?: boolean;
193
213
  }
@@ -0,0 +1,18 @@
1
+ import type { DropdownMenuProps } from './types';
2
+ /**
3
+ * Top-layer implementation of DropdownMenu.
4
+ *
5
+ * Replaces the legacy `@atlaskit/popup` + `@atlaskit/portal` + `@atlaskit/layering` pipeline
6
+ * with native Popover API via `@atlaskit/top-layer`.
7
+ *
8
+ * What's no longer needed:
9
+ * - Portal: top layer handles stacking natively
10
+ * - FocusLock / react-focus-lock: popover=auto provides light dismiss
11
+ * - z-index: top layer is always above everything
12
+ * - FocusManager (ref registration): replaced by DOM-query-based `useArrowNavigation`
13
+ * - handle-focus.tsx: replaced by `useArrowNavigation`
14
+ * - Layering context: top layer nesting is handled by the browser
15
+ * - Fallback placements / Popper: CSS Anchor Positioning handles positioning
16
+ */
17
+ declare function DropdownMenuTopLayer({ autoFocus, children, defaultOpen, isLoading, isOpen: isOpenProp, onOpenChange, placement, shouldFitContainer, returnFocusRef, spacing, statusLabel, testId, trigger, label, interactionName, menuLabel, }: DropdownMenuProps): React.JSX.Element;
18
+ export default DropdownMenuTopLayer;
@@ -9,5 +9,5 @@ import type { DropdownMenuProps } from './types';
9
9
  * - [Code](https://atlassian.design/components/dropdown-menu/code)
10
10
  * - [Usage](https://atlassian.design/components/dropdown-menu/usage)
11
11
  */
12
- declare const DropdownMenu: <T extends HTMLElement = any>({ autoFocus, children, defaultOpen, isLoading, isOpen, onOpenChange, placement, shouldFitContainer, shouldFlip, shouldRenderToParent, returnFocusRef, spacing, statusLabel, testId, trigger, zIndex, label, interactionName, strategy, menuLabel, shouldPreventEscapePropagation, }: DropdownMenuProps<T>) => React.JSX.Element;
12
+ declare const DropdownMenu: <T extends HTMLElement = any>(props: DropdownMenuProps<T>) => React.JSX.Element;
13
13
  export default DropdownMenu;
@@ -2,7 +2,12 @@ import React from 'react';
2
2
  /**
3
3
  * __Group title__
4
4
  *
5
- * Used to visually represent the title for DropdownMenu groups
5
+ * Used to visually represent the title for DropdownMenu groups.
6
+ *
7
+ * Pre-existing a11y note: uses `role="menuitem"` with `aria-hidden="true"`.
8
+ * WAI-ARIA APG recommends `role="presentation"` for non-interactive group
9
+ * headings, with the group linked via `aria-labelledby`. Out of scope for
10
+ * the top-layer migration — this is legacy behavior.
6
11
  *
7
12
  * @internal
8
13
  */
@@ -0,0 +1,2 @@
1
+ export { useArrowNavigation, isAtCurrentMenuLevel, type TUseArrowNavigationArgs } from './use-arrow-navigation';
2
+ export { getFirstFocusable } from '@atlaskit/top-layer/focus';
@@ -0,0 +1 @@
1
+ export { useArrowNavigation, isAtCurrentMenuLevel, type TUseArrowNavigationArgs, } from '@atlaskit/top-layer/use-arrow-navigation';
@@ -114,6 +114,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
114
114
  /**
115
115
  * Allows the dropdown menu to be placed on the opposite side of its trigger if it does not
116
116
  * fit in the viewport.
117
+ *
118
+ * @private
119
+ * @deprecated No-op when `platform-dst-top-layer` is enabled — CSS Anchor Positioning
120
+ * handles flipping natively via `position-try-fallbacks`.
117
121
  */
118
122
  shouldFlip?: boolean;
119
123
  /**
@@ -121,6 +125,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
121
125
  * `true` renders the dropdown menu in the DOM node closest to the trigger; focus is not trapped inside the element.
122
126
  * `false` renders the dropdown menu in React.Portal and focus is trapped inside the element.
123
127
  * Defaults to `false`.
128
+ *
129
+ * @private
130
+ * @deprecated No-op when `platform-dst-top-layer` is enabled — content always
131
+ * renders in the browser's top layer.
124
132
  */
125
133
  shouldRenderToParent?: boolean;
126
134
  /**
@@ -162,6 +170,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
162
170
  * Z-index that the popup should be displayed in.
163
171
  * This is passed to the portal component.
164
172
  * Defaults to `layers.modal()` from `@atlaskit/theme` which is 510.
173
+ *
174
+ * @private
175
+ * @deprecated No-op when `platform-dst-top-layer` is enabled — the browser's
176
+ * top layer manages stacking without z-index.
165
177
  */
166
178
  zIndex?: number;
167
179
  /**
@@ -179,6 +191,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
179
191
  /**
180
192
  * This controls the positioning strategy to use. Can vary between `absolute` and `fixed`.
181
193
  * The default is `fixed`.
194
+ *
195
+ * @private
196
+ * @deprecated No-op when `platform-dst-top-layer` is enabled — CSS Anchor Positioning
197
+ * replaces Popper's positioning strategy.
182
198
  */
183
199
  strategy?: 'absolute' | 'fixed';
184
200
  /**
@@ -188,6 +204,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
188
204
  /**
189
205
  * When set to true, will call stopPropagation on the ESCAPE key event.
190
206
  * This prevents the ESCAPE event from bubbling up to parent elements.
207
+ *
208
+ * @private
209
+ * @deprecated No-op when `platform-dst-top-layer` is enabled — the native
210
+ * popover dismiss handles Escape propagation.
191
211
  */
192
212
  shouldPreventEscapePropagation?: boolean;
193
213
  }