@atlaskit/react-select 4.0.2 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/cjs/components/menu-placer.js +7 -0
- package/dist/cjs/components/menu-portal-top-layer.compiled.css +1 -0
- package/dist/cjs/components/menu-portal-top-layer.js +151 -0
- package/dist/cjs/components/menu-portal.js +22 -2
- package/dist/cjs/internal/menu-portal-close-context.js +13 -0
- package/dist/cjs/select.js +157 -54
- package/dist/es2019/components/menu-placer.js +7 -0
- package/dist/es2019/components/menu-portal-top-layer.compiled.css +1 -0
- package/dist/es2019/components/menu-portal-top-layer.js +143 -0
- package/dist/es2019/components/menu-portal.js +22 -2
- package/dist/es2019/internal/menu-portal-close-context.js +8 -0
- package/dist/es2019/select.js +106 -11
- package/dist/esm/components/menu-placer.js +7 -0
- package/dist/esm/components/menu-portal-top-layer.compiled.css +1 -0
- package/dist/esm/components/menu-portal-top-layer.js +142 -0
- package/dist/esm/components/menu-portal.js +22 -2
- package/dist/esm/internal/menu-portal-close-context.js +8 -0
- package/dist/esm/select.js +157 -54
- package/dist/types/components/menu-portal-top-layer.d.ts +21 -0
- package/dist/types/components/menu-portal.d.ts +6 -1
- package/dist/types/internal/menu-portal-close-context.d.ts +7 -0
- package/dist/types/select.d.ts +34 -0
- package/package.json +13 -2
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Internal handoff from `Select` to `MenuPortalTopLayer` for the top-layer
|
|
5
|
+
* dismiss signal. Kept off `MenuPortalProps` to avoid widening the public
|
|
6
|
+
* subpath export at `@atlaskit/react-select/menu-portal`.
|
|
7
|
+
*/
|
|
8
|
+
export const MenuPortalCloseContext = /*#__PURE__*/createContext(undefined);
|
package/dist/es2019/select.js
CHANGED
|
@@ -4,6 +4,7 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
|
4
4
|
import "./select.compiled.css";
|
|
5
5
|
import { ax, ix } from "@compiled/react/runtime";
|
|
6
6
|
import React, { Component } from 'react';
|
|
7
|
+
import { bind } from 'bind-event-listener';
|
|
7
8
|
import { isAppleDevice } from '@atlaskit/ds-lib/device-check';
|
|
8
9
|
import { isSafari } from '@atlaskit/ds-lib/is-safari';
|
|
9
10
|
import __noop from '@atlaskit/ds-lib/noop';
|
|
@@ -17,6 +18,7 @@ import { createFilter } from './filters';
|
|
|
17
18
|
import { classNames } from './internal/classnames';
|
|
18
19
|
import { cleanValue } from './internal/clean-value';
|
|
19
20
|
import { isDocumentElement } from './internal/is-document-el';
|
|
21
|
+
import { MenuPortalCloseContext } from './internal/menu-portal-close-context';
|
|
20
22
|
import { multiValueAsValue } from './internal/multi-value-as-value';
|
|
21
23
|
import { NotifyOpenLayerObserver } from './internal/notify-open-layer-observer';
|
|
22
24
|
import RequiredInput from './internal/required-input';
|
|
@@ -303,7 +305,8 @@ export default class Select extends Component {
|
|
|
303
305
|
prevWasFocused: false,
|
|
304
306
|
inputIsHiddenAfterUpdate: undefined,
|
|
305
307
|
prevProps: undefined,
|
|
306
|
-
instancePrefix: ''
|
|
308
|
+
instancePrefix: '',
|
|
309
|
+
controlElement: null
|
|
307
310
|
});
|
|
308
311
|
// Misc. Instance Properties
|
|
309
312
|
// ------------------------------
|
|
@@ -314,11 +317,26 @@ export default class Select extends Component {
|
|
|
314
317
|
_defineProperty(this, "initialTouchY", 0);
|
|
315
318
|
_defineProperty(this, "openAfterFocus", false);
|
|
316
319
|
_defineProperty(this, "scrollToFocusedOptionOnUpdate", false);
|
|
320
|
+
// Cleanup for a pending document `pointerup` listener registered by
|
|
321
|
+
// `openMenuAfterPointerUp`. See that method for the full rationale.
|
|
322
|
+
_defineProperty(this, "deferredOpenMenuCleanup", null);
|
|
317
323
|
// Refs
|
|
318
324
|
// ------------------------------
|
|
319
325
|
_defineProperty(this, "controlRef", null);
|
|
320
326
|
_defineProperty(this, "getControlRef", ref => {
|
|
321
327
|
this.controlRef = ref;
|
|
328
|
+
// Mirror the ref into state on the top-layer path so
|
|
329
|
+
// `MenuPortalTopLayer`'s layout effects react to the anchor attaching.
|
|
330
|
+
// Skip the null-ref (unmount) case: setState during unmount is unsafe
|
|
331
|
+
// and the state is dropped with the instance anyway.
|
|
332
|
+
if (ref === null) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (this.state.controlElement !== ref && fg('platform-dst-top-layer')) {
|
|
336
|
+
this.setState({
|
|
337
|
+
controlElement: ref
|
|
338
|
+
});
|
|
339
|
+
}
|
|
322
340
|
});
|
|
323
341
|
_defineProperty(this, "focusedOptionRef", null);
|
|
324
342
|
_defineProperty(this, "getFocusedOptionRef", ref => {
|
|
@@ -528,7 +546,7 @@ export default class Select extends Component {
|
|
|
528
546
|
this.focusInput();
|
|
529
547
|
} else if (!this.props.menuIsOpen) {
|
|
530
548
|
if (openMenuOnClick) {
|
|
531
|
-
this.
|
|
549
|
+
this.openMenuAfterPointerUp('first');
|
|
532
550
|
}
|
|
533
551
|
} else {
|
|
534
552
|
if (event.target.tagName !== 'INPUT' && event.target.tagName !== 'TEXTAREA') {
|
|
@@ -558,7 +576,7 @@ export default class Select extends Component {
|
|
|
558
576
|
});
|
|
559
577
|
this.onMenuClose();
|
|
560
578
|
} else {
|
|
561
|
-
this.
|
|
579
|
+
this.openMenuAfterPointerUp('first');
|
|
562
580
|
}
|
|
563
581
|
event.preventDefault();
|
|
564
582
|
});
|
|
@@ -678,7 +696,14 @@ export default class Select extends Component {
|
|
|
678
696
|
isFocused: true
|
|
679
697
|
});
|
|
680
698
|
if (this.openAfterFocus || this.props.openMenuOnFocus) {
|
|
681
|
-
|
|
699
|
+
// `openAfterFocus` always follows a pointer gesture, so defer past
|
|
700
|
+
// pointerup. `openMenuOnFocus` alone can come from a keyboard tab
|
|
701
|
+
// with no pointer gesture in flight, so open synchronously.
|
|
702
|
+
if (this.openAfterFocus) {
|
|
703
|
+
this.openMenuAfterPointerUp('first');
|
|
704
|
+
} else {
|
|
705
|
+
this.openMenu('first');
|
|
706
|
+
}
|
|
682
707
|
}
|
|
683
708
|
this.openAfterFocus = false;
|
|
684
709
|
});
|
|
@@ -1033,6 +1058,7 @@ export default class Select extends Component {
|
|
|
1033
1058
|
componentWillUnmount() {
|
|
1034
1059
|
this.stopListeningComposition();
|
|
1035
1060
|
this.stopListeningToTouch();
|
|
1061
|
+
this.cancelDeferredOpenMenu();
|
|
1036
1062
|
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
1037
1063
|
document.removeEventListener('scroll', this.onScroll, true);
|
|
1038
1064
|
}
|
|
@@ -1071,6 +1097,60 @@ export default class Select extends Component {
|
|
|
1071
1097
|
}
|
|
1072
1098
|
this.inputRef.blur();
|
|
1073
1099
|
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Whether to defer the menu open past the in-flight pointer gesture.
|
|
1102
|
+
* Any renderer that drives a `popover="auto"` element must, otherwise
|
|
1103
|
+
* the browser's light-dismiss runs on the matching `pointerup` and
|
|
1104
|
+
* closes the menu immediately. Today only the top-layer path needs it.
|
|
1105
|
+
*/
|
|
1106
|
+
shouldDeferOpenPastPointerUp() {
|
|
1107
|
+
return fg('platform-dst-top-layer');
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* Open the menu after the current pointer gesture, instead of
|
|
1112
|
+
* synchronously inside `mousedown`.
|
|
1113
|
+
*
|
|
1114
|
+
* On the top-layer path the menu is a `popover="auto"` element. The
|
|
1115
|
+
* browser captures the pointerdown target before the popover exists, so
|
|
1116
|
+
* opening synchronously gets immediately light-dismissed on pointerup
|
|
1117
|
+
* (and the matching `beforetoggle: closed` is not cancellable). Deferring
|
|
1118
|
+
* to the next `pointerup` avoids that.
|
|
1119
|
+
*
|
|
1120
|
+
* We listen for `pointerup` rather than `click` because `pointerup` is
|
|
1121
|
+
* the exact event the browser uses for light-dismiss (earliest safe
|
|
1122
|
+
* moment), always fires (`click` requires same down/up target), is hard
|
|
1123
|
+
* to lose to upstream `stopPropagation`, and is uniform across input
|
|
1124
|
+
* types. Off the top-layer path we open synchronously as before.
|
|
1125
|
+
*/
|
|
1126
|
+
openMenuAfterPointerUp(focusOption) {
|
|
1127
|
+
if (!this.shouldDeferOpenPastPointerUp()) {
|
|
1128
|
+
this.openMenu(focusOption);
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
// A second pointerdown can land before the queued pointerup if the
|
|
1132
|
+
// user releases and re-clicks very quickly. Replace any pending
|
|
1133
|
+
// deferred open with the latest one so we never stack listeners.
|
|
1134
|
+
this.cancelDeferredOpenMenu();
|
|
1135
|
+
const handlePointerUp = () => {
|
|
1136
|
+
this.deferredOpenMenuCleanup = null;
|
|
1137
|
+
this.openMenu(focusOption);
|
|
1138
|
+
};
|
|
1139
|
+
this.deferredOpenMenuCleanup = bind(document, {
|
|
1140
|
+
type: 'pointerup',
|
|
1141
|
+
listener: handlePointerUp,
|
|
1142
|
+
options: {
|
|
1143
|
+
capture: true,
|
|
1144
|
+
once: true
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
cancelDeferredOpenMenu() {
|
|
1149
|
+
if (this.deferredOpenMenuCleanup) {
|
|
1150
|
+
this.deferredOpenMenuCleanup();
|
|
1151
|
+
this.deferredOpenMenuCleanup = null;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1074
1154
|
openMenu(focusOption) {
|
|
1075
1155
|
const {
|
|
1076
1156
|
selectValue,
|
|
@@ -1877,15 +1957,30 @@ export default class Select extends Component {
|
|
|
1877
1957
|
}, menuUI) : menuUI);
|
|
1878
1958
|
})));
|
|
1879
1959
|
|
|
1880
|
-
//
|
|
1881
|
-
//
|
|
1882
|
-
//
|
|
1883
|
-
|
|
1960
|
+
// On the top-layer path the menu always portals (into the top layer)
|
|
1961
|
+
// regardless of consumer `menuPortalTarget` / `menuPosition`. Off the
|
|
1962
|
+
// flag we keep the legacy "portal only when needed" behaviour.
|
|
1963
|
+
const shouldPortal = fg('platform-dst-top-layer') || menuPortalTarget || menuPosition === 'fixed';
|
|
1964
|
+
if (!shouldPortal) {
|
|
1965
|
+
return menuElement;
|
|
1966
|
+
}
|
|
1967
|
+
// Top-layer path needs the state mirror so MenuPortalTopLayer re-renders
|
|
1968
|
+
// when the anchor attaches; legacy path keeps the direct ref read.
|
|
1969
|
+
const controlElementForPortal = fg('platform-dst-top-layer') ? this.state.controlElement : this.controlRef;
|
|
1970
|
+
const menuPortal = /*#__PURE__*/React.createElement(MenuPortal, _extends({}, commonProps, {
|
|
1884
1971
|
appendTo: menuPortalTarget,
|
|
1885
|
-
controlElement:
|
|
1972
|
+
controlElement: controlElementForPortal,
|
|
1886
1973
|
menuPlacement: menuPlacement,
|
|
1887
1974
|
menuPosition: menuPosition
|
|
1888
|
-
}), menuElement)
|
|
1975
|
+
}), menuElement);
|
|
1976
|
+
// The Provider plumbs the close signal to MenuPortalTopLayer; not
|
|
1977
|
+
// needed on the legacy path.
|
|
1978
|
+
if (!fg('platform-dst-top-layer')) {
|
|
1979
|
+
return menuPortal;
|
|
1980
|
+
}
|
|
1981
|
+
return /*#__PURE__*/React.createElement(MenuPortalCloseContext.Provider, {
|
|
1982
|
+
value: this.handleOpenLayerObserverCloseSignal
|
|
1983
|
+
}, menuPortal);
|
|
1889
1984
|
}
|
|
1890
1985
|
renderFormField() {
|
|
1891
1986
|
const {
|
|
@@ -2040,7 +2135,7 @@ export default class Select extends Component {
|
|
|
2040
2135
|
'data-testid': `${testId}-select--indicators-container`
|
|
2041
2136
|
})
|
|
2042
2137
|
}
|
|
2043
|
-
}), this.renderClearIndicator(), this.renderLoadingIndicator(), this.renderDropdownIndicator())), this.renderMenu(), this.renderFormField(), /*#__PURE__*/React.createElement(NotifyOpenLayerObserver, {
|
|
2138
|
+
}), this.renderClearIndicator(), this.renderLoadingIndicator(), this.renderDropdownIndicator())), this.renderMenu(), this.renderFormField(), !fg('platform-dst-top-layer') && /*#__PURE__*/React.createElement(NotifyOpenLayerObserver, {
|
|
2044
2139
|
isOpen: this.props.menuIsOpen,
|
|
2045
2140
|
onClose: this.handleOpenLayerObserverCloseSignal
|
|
2046
2141
|
})));
|
|
@@ -4,6 +4,7 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
|
|
|
4
4
|
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; }
|
|
5
5
|
import { useContext, useLayoutEffect, useRef, useState } from 'react';
|
|
6
6
|
import __noop from '@atlaskit/ds-lib/noop';
|
|
7
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
7
8
|
import { PortalPlacementContext } from '../internal/portal-placement-context';
|
|
8
9
|
var noop = __noop;
|
|
9
10
|
function getScrollParent(element) {
|
|
@@ -262,6 +263,12 @@ var MenuPlacer = function MenuPlacer(props) {
|
|
|
262
263
|
// The minimum height of the control
|
|
263
264
|
var controlHeight = 38;
|
|
264
265
|
useLayoutEffect(function () {
|
|
266
|
+
// When the menu is hosted in the browser top layer, positioning, flipping
|
|
267
|
+
// and viewport-fit are all handled by `@atlaskit/top-layer`. The placer
|
|
268
|
+
// becomes a pass-through that only forwards `maxMenuHeight` as a cap.
|
|
269
|
+
if (fg('platform-dst-top-layer')) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
265
272
|
var menuEl = ref.current;
|
|
266
273
|
if (!menuEl) {
|
|
267
274
|
return;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
._ofie1496{max-block-size:100dvh}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/* menu-portal-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 "./menu-portal-top-layer.compiled.css";
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { ax, ix } from "@compiled/react/runtime";
|
|
7
|
+
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; }
|
|
8
|
+
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; }
|
|
9
|
+
import { useCallback, useContext, useRef } from 'react';
|
|
10
|
+
import { cx } from '@compiled/react';
|
|
11
|
+
import { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer';
|
|
12
|
+
import { Popover } from '@atlaskit/top-layer/popover';
|
|
13
|
+
import { useAnchorPosition } from '@atlaskit/top-layer/use-anchor-position';
|
|
14
|
+
import { useWidthFromAnchor } from '@atlaskit/top-layer/use-width-from-anchor';
|
|
15
|
+
import { getStyleProps } from '../get-style-props';
|
|
16
|
+
import { MenuPortalCloseContext } from '../internal/menu-portal-close-context';
|
|
17
|
+
// `'auto'` falls through to `'end'`; top-layer's `position-try-fallbacks` flips it if needed.
|
|
18
|
+
function reactSelectEdgeToTopLayerEdge(menuPlacement) {
|
|
19
|
+
return menuPlacement === 'top' ? 'start' : 'end';
|
|
20
|
+
}
|
|
21
|
+
var menuPortalStyles = {
|
|
22
|
+
root: "_ofie1496"
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Top-layer host for react-select's menu. Hands positioning, flip, and width
|
|
27
|
+
* to `@atlaskit/top-layer`; ignores `appendTo` / `menuPortalTarget` /
|
|
28
|
+
* `menuPosition`. The browser-dismiss handler is supplied internally by
|
|
29
|
+
* `Select` via `MenuPortalCloseContext`.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* <MenuPortalTopLayer controlElement={el} menuPlacement="bottom">
|
|
34
|
+
* <Menu />
|
|
35
|
+
* </MenuPortalTopLayer>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function MenuPortalTopLayer(props) {
|
|
39
|
+
var children = props.children,
|
|
40
|
+
controlElement = props.controlElement,
|
|
41
|
+
innerProps = props.innerProps,
|
|
42
|
+
menuPlacement = props.menuPlacement,
|
|
43
|
+
menuPosition = props.menuPosition,
|
|
44
|
+
xcss = props.xcss;
|
|
45
|
+
// Select's "close the menu" callback, distinct from `Popover.onClose`.
|
|
46
|
+
var closeSelect = useContext(MenuPortalCloseContext);
|
|
47
|
+
var popoverRef = useRef(null);
|
|
48
|
+
|
|
49
|
+
// Top-layer hooks need a RefObject; in-render mutation keeps it in sync
|
|
50
|
+
// with the prop without an effect. The hooks re-read `.current` from
|
|
51
|
+
// `isOpen`-keyed layout effects, so a stable ref identity is fine.
|
|
52
|
+
var anchorRef = useRef(null);
|
|
53
|
+
anchorRef.current = controlElement;
|
|
54
|
+
|
|
55
|
+
// `controlElement` is null on initial mount / SSR until `Select` mirrors
|
|
56
|
+
// its ref into state. While null, every hook below no-ops to avoid DOM
|
|
57
|
+
// reads or registering an unpositioned popover.
|
|
58
|
+
var isAnchored = controlElement !== null;
|
|
59
|
+
|
|
60
|
+
// `gap: 0` matches the legacy MenuPortal (no trigger-to-menu gap; the
|
|
61
|
+
// menu root already declares its own `marginBlockStart`). Without this
|
|
62
|
+
// override `useAnchorPosition`'s default 8px gap would diverge visually
|
|
63
|
+
// from the legacy path.
|
|
64
|
+
useAnchorPosition({
|
|
65
|
+
anchorRef: anchorRef,
|
|
66
|
+
popoverRef: popoverRef,
|
|
67
|
+
placement: {
|
|
68
|
+
axis: 'block',
|
|
69
|
+
edge: reactSelectEdgeToTopLayerEdge(menuPlacement),
|
|
70
|
+
offset: {
|
|
71
|
+
gap: 0
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
isOpen: isAnchored
|
|
75
|
+
});
|
|
76
|
+
useWidthFromAnchor({
|
|
77
|
+
anchorRef: anchorRef,
|
|
78
|
+
popoverRef: popoverRef,
|
|
79
|
+
mode: 'match-anchor',
|
|
80
|
+
isOpen: isAnchored
|
|
81
|
+
});
|
|
82
|
+
var handlePopoverClose = useCallback(function () {
|
|
83
|
+
if (closeSelect) {
|
|
84
|
+
closeSelect();
|
|
85
|
+
}
|
|
86
|
+
}, [closeSelect]);
|
|
87
|
+
|
|
88
|
+
// Explicit observer registration: the outer Popover is intentionally
|
|
89
|
+
// roleless (see Popover comment below), so Popover cannot auto-register
|
|
90
|
+
// from its role. This lets `closeLayers()` (Modal / Drawer) dismiss us.
|
|
91
|
+
useNotifyOpenLayerObserver({
|
|
92
|
+
type: 'popup',
|
|
93
|
+
isOpen: isAnchored,
|
|
94
|
+
onClose: handlePopoverClose
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Top-layer owns positioning; zero offset/rect preserves the consumer
|
|
98
|
+
// styles call shape.
|
|
99
|
+
var _getStyleProps = getStyleProps(_objectSpread(_objectSpread({}, props), {}, {
|
|
100
|
+
offset: 0,
|
|
101
|
+
position: menuPosition,
|
|
102
|
+
rect: {
|
|
103
|
+
left: 0,
|
|
104
|
+
width: 0
|
|
105
|
+
}
|
|
106
|
+
}), 'menuPortal', {
|
|
107
|
+
'menu-portal': true
|
|
108
|
+
}),
|
|
109
|
+
className = _getStyleProps.className;
|
|
110
|
+
|
|
111
|
+
// Popover stays mounted and is driven by `isOpen` so its layout-effect
|
|
112
|
+
// teardown (`hidePopover`, observer cleanup, position-hook style reset)
|
|
113
|
+
// runs against a live element. Conditional render would skip that path.
|
|
114
|
+
//
|
|
115
|
+
// `mode="manual"` opts out of native light-dismiss: react-select already
|
|
116
|
+
// owns outside-click and Escape via its own handlers, and the combobox
|
|
117
|
+
// trigger lives in a separate DOM subtree that the spec algorithm cannot
|
|
118
|
+
// see. Matches the pattern in `@atlaskit/datetime-picker`'s MenuTopLayer.
|
|
119
|
+
//
|
|
120
|
+
// The Popover host is intentionally roleless: the inner `MenuList`
|
|
121
|
+
// keeps `role="listbox"` and the id referenced by `aria-controls`.
|
|
122
|
+
// Putting the role on the outer host caused Playwright `toBeVisible`
|
|
123
|
+
// and some SR hit-testing to treat it as hidden (zero bounding rect
|
|
124
|
+
// while `styles.root` opts out of UA `[popover]` positioning).
|
|
125
|
+
//
|
|
126
|
+
// TODO: add open / close animation. Select unmounts MenuPortalTopLayer
|
|
127
|
+
// the moment `menuIsOpen` flips false, so Popover's `animate` exit
|
|
128
|
+
// transition never gets a frame. Needs keeping the portal mounted
|
|
129
|
+
// through the exit (`onExitFinish`) on the Select side.
|
|
130
|
+
return /*#__PURE__*/React.createElement(Popover, {
|
|
131
|
+
ref: popoverRef,
|
|
132
|
+
mode: "manual",
|
|
133
|
+
isOpen: isAnchored,
|
|
134
|
+
onClose: handlePopoverClose
|
|
135
|
+
}, /*#__PURE__*/React.createElement("div", _extends({
|
|
136
|
+
// `className` carries consumer `styles.menuPortal({...})` output,
|
|
137
|
+
// `xcss` carries caller compiled atomic classes, and `-MenuPortal`
|
|
138
|
+
// is a legacy selector hook kept for parity with MenuPortalLegacy.
|
|
139
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop, @atlaskit/ui-styling-standard/local-cx-xcss, @compiled/local-cx-xcss, @typescript-eslint/no-explicit-any
|
|
140
|
+
className: ax([menuPortalStyles.root, cx(className, xcss, '-MenuPortal')])
|
|
141
|
+
}, innerProps), children));
|
|
142
|
+
}
|
|
@@ -11,8 +11,10 @@ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
|
11
11
|
import { cx } from '@compiled/react';
|
|
12
12
|
import { autoUpdate } from '@floating-ui/dom';
|
|
13
13
|
import { createPortal } from 'react-dom';
|
|
14
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
14
15
|
import { getStyleProps } from '../get-style-props';
|
|
15
16
|
import { PortalPlacementContext } from '../internal/portal-placement-context';
|
|
17
|
+
import { MenuPortalTopLayer } from './menu-portal-top-layer';
|
|
16
18
|
function getBoundingClientObj(element) {
|
|
17
19
|
var rect = element.getBoundingClientRect();
|
|
18
20
|
return {
|
|
@@ -31,7 +33,7 @@ var menuPortalStyles = {
|
|
|
31
33
|
root: "_1pbykb7n _1e02a1vk _kqswcp1v _152t1nmo _1bsb1qxj"
|
|
32
34
|
};
|
|
33
35
|
// eslint-disable-next-line @repo/internal/react/require-jsdoc
|
|
34
|
-
|
|
36
|
+
function MenuPortalLegacy(props) {
|
|
35
37
|
var appendTo = props.appendTo,
|
|
36
38
|
children = props.children,
|
|
37
39
|
controlElement = props.controlElement,
|
|
@@ -90,7 +92,10 @@ export var MenuPortal = function MenuPortal(props) {
|
|
|
90
92
|
runAutoUpdate();
|
|
91
93
|
}, [runAutoUpdate]);
|
|
92
94
|
|
|
93
|
-
//
|
|
95
|
+
// Legacy quirk: `computedPosition` is null until the layout effect runs,
|
|
96
|
+
// so the first render returns null even with `defaultMenuIsOpen` set.
|
|
97
|
+
// Synchronous observers (VR snapshots) see a one-frame "closed" state.
|
|
98
|
+
// Left as-is; the top-layer path supersedes this and positions declaratively.
|
|
94
99
|
if (!appendTo && menuPosition !== 'fixed' || !computedPosition) {
|
|
95
100
|
return null;
|
|
96
101
|
}
|
|
@@ -120,4 +125,19 @@ export var MenuPortal = function MenuPortal(props) {
|
|
|
120
125
|
return /*#__PURE__*/React.createElement(PortalPlacementContext.Provider, {
|
|
121
126
|
value: portalPlacementContext
|
|
122
127
|
}, appendTo ? /*#__PURE__*/createPortal(menuWrapper, appendTo) : menuWrapper);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Public-facing `MenuPortal` component. Routes between the legacy
|
|
132
|
+
* `createPortal`-based implementation and the top-layer-based
|
|
133
|
+
* `MenuPortalTopLayer` based on the `platform-dst-top-layer` feature flag.
|
|
134
|
+
*/
|
|
135
|
+
// eslint-disable-next-line @repo/internal/react/require-jsdoc
|
|
136
|
+
export var MenuPortal = function MenuPortal(props) {
|
|
137
|
+
if (fg('platform-dst-top-layer')) {
|
|
138
|
+
// eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
|
|
139
|
+
return /*#__PURE__*/React.createElement(MenuPortalTopLayer, props);
|
|
140
|
+
}
|
|
141
|
+
// eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
|
|
142
|
+
return /*#__PURE__*/React.createElement(MenuPortalLegacy, props);
|
|
123
143
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Internal handoff from `Select` to `MenuPortalTopLayer` for the top-layer
|
|
5
|
+
* dismiss signal. Kept off `MenuPortalProps` to avoid widening the public
|
|
6
|
+
* subpath export at `@atlaskit/react-select/menu-portal`.
|
|
7
|
+
*/
|
|
8
|
+
export var MenuPortalCloseContext = /*#__PURE__*/createContext(undefined);
|