@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
package/dist/esm/select.js
CHANGED
|
@@ -16,6 +16,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
16
16
|
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
|
|
17
17
|
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
|
|
18
18
|
import React, { Component } from 'react';
|
|
19
|
+
import { bind } from 'bind-event-listener';
|
|
19
20
|
import { isAppleDevice } from '@atlaskit/ds-lib/device-check';
|
|
20
21
|
import { isSafari } from '@atlaskit/ds-lib/is-safari';
|
|
21
22
|
import __noop from '@atlaskit/ds-lib/noop';
|
|
@@ -29,6 +30,7 @@ import { createFilter } from './filters';
|
|
|
29
30
|
import { classNames } from './internal/classnames';
|
|
30
31
|
import { cleanValue } from './internal/clean-value';
|
|
31
32
|
import { isDocumentElement } from './internal/is-document-el';
|
|
33
|
+
import { MenuPortalCloseContext } from './internal/menu-portal-close-context';
|
|
32
34
|
import { multiValueAsValue } from './internal/multi-value-as-value';
|
|
33
35
|
import { NotifyOpenLayerObserver } from './internal/notify-open-layer-observer';
|
|
34
36
|
import RequiredInput from './internal/required-input';
|
|
@@ -327,7 +329,8 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
327
329
|
prevWasFocused: false,
|
|
328
330
|
inputIsHiddenAfterUpdate: undefined,
|
|
329
331
|
prevProps: undefined,
|
|
330
|
-
instancePrefix: ''
|
|
332
|
+
instancePrefix: '',
|
|
333
|
+
controlElement: null
|
|
331
334
|
});
|
|
332
335
|
// Misc. Instance Properties
|
|
333
336
|
// ------------------------------
|
|
@@ -338,11 +341,26 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
338
341
|
_defineProperty(_this, "initialTouchY", 0);
|
|
339
342
|
_defineProperty(_this, "openAfterFocus", false);
|
|
340
343
|
_defineProperty(_this, "scrollToFocusedOptionOnUpdate", false);
|
|
344
|
+
// Cleanup for a pending document `pointerup` listener registered by
|
|
345
|
+
// `openMenuAfterPointerUp`. See that method for the full rationale.
|
|
346
|
+
_defineProperty(_this, "deferredOpenMenuCleanup", null);
|
|
341
347
|
// Refs
|
|
342
348
|
// ------------------------------
|
|
343
349
|
_defineProperty(_this, "controlRef", null);
|
|
344
350
|
_defineProperty(_this, "getControlRef", function (ref) {
|
|
345
351
|
_this.controlRef = ref;
|
|
352
|
+
// Mirror the ref into state on the top-layer path so
|
|
353
|
+
// `MenuPortalTopLayer`'s layout effects react to the anchor attaching.
|
|
354
|
+
// Skip the null-ref (unmount) case: setState during unmount is unsafe
|
|
355
|
+
// and the state is dropped with the instance anyway.
|
|
356
|
+
if (ref === null) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (_this.state.controlElement !== ref && fg('platform-dst-top-layer')) {
|
|
360
|
+
_this.setState({
|
|
361
|
+
controlElement: ref
|
|
362
|
+
});
|
|
363
|
+
}
|
|
346
364
|
});
|
|
347
365
|
_defineProperty(_this, "focusedOptionRef", null);
|
|
348
366
|
_defineProperty(_this, "getFocusedOptionRef", function (ref) {
|
|
@@ -553,7 +571,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
553
571
|
_this.focusInput();
|
|
554
572
|
} else if (!_this.props.menuIsOpen) {
|
|
555
573
|
if (openMenuOnClick) {
|
|
556
|
-
_this.
|
|
574
|
+
_this.openMenuAfterPointerUp('first');
|
|
557
575
|
}
|
|
558
576
|
} else {
|
|
559
577
|
if (event.target.tagName !== 'INPUT' && event.target.tagName !== 'TEXTAREA') {
|
|
@@ -582,7 +600,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
582
600
|
});
|
|
583
601
|
_this.onMenuClose();
|
|
584
602
|
} else {
|
|
585
|
-
_this.
|
|
603
|
+
_this.openMenuAfterPointerUp('first');
|
|
586
604
|
}
|
|
587
605
|
event.preventDefault();
|
|
588
606
|
});
|
|
@@ -700,7 +718,14 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
700
718
|
isFocused: true
|
|
701
719
|
});
|
|
702
720
|
if (_this.openAfterFocus || _this.props.openMenuOnFocus) {
|
|
703
|
-
|
|
721
|
+
// `openAfterFocus` always follows a pointer gesture, so defer past
|
|
722
|
+
// pointerup. `openMenuOnFocus` alone can come from a keyboard tab
|
|
723
|
+
// with no pointer gesture in flight, so open synchronously.
|
|
724
|
+
if (_this.openAfterFocus) {
|
|
725
|
+
_this.openMenuAfterPointerUp('first');
|
|
726
|
+
} else {
|
|
727
|
+
_this.openMenu('first');
|
|
728
|
+
}
|
|
704
729
|
}
|
|
705
730
|
_this.openAfterFocus = false;
|
|
706
731
|
});
|
|
@@ -991,6 +1016,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
991
1016
|
value: function componentWillUnmount() {
|
|
992
1017
|
this.stopListeningComposition();
|
|
993
1018
|
this.stopListeningToTouch();
|
|
1019
|
+
this.cancelDeferredOpenMenu();
|
|
994
1020
|
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
995
1021
|
document.removeEventListener('scroll', this.onScroll, true);
|
|
996
1022
|
}
|
|
@@ -1037,10 +1063,72 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1037
1063
|
}
|
|
1038
1064
|
this.inputRef.blur();
|
|
1039
1065
|
}
|
|
1066
|
+
}, {
|
|
1067
|
+
key: "shouldDeferOpenPastPointerUp",
|
|
1068
|
+
value:
|
|
1069
|
+
/**
|
|
1070
|
+
* Whether to defer the menu open past the in-flight pointer gesture.
|
|
1071
|
+
* Any renderer that drives a `popover="auto"` element must, otherwise
|
|
1072
|
+
* the browser's light-dismiss runs on the matching `pointerup` and
|
|
1073
|
+
* closes the menu immediately. Today only the top-layer path needs it.
|
|
1074
|
+
*/
|
|
1075
|
+
function shouldDeferOpenPastPointerUp() {
|
|
1076
|
+
return fg('platform-dst-top-layer');
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* Open the menu after the current pointer gesture, instead of
|
|
1081
|
+
* synchronously inside `mousedown`.
|
|
1082
|
+
*
|
|
1083
|
+
* On the top-layer path the menu is a `popover="auto"` element. The
|
|
1084
|
+
* browser captures the pointerdown target before the popover exists, so
|
|
1085
|
+
* opening synchronously gets immediately light-dismissed on pointerup
|
|
1086
|
+
* (and the matching `beforetoggle: closed` is not cancellable). Deferring
|
|
1087
|
+
* to the next `pointerup` avoids that.
|
|
1088
|
+
*
|
|
1089
|
+
* We listen for `pointerup` rather than `click` because `pointerup` is
|
|
1090
|
+
* the exact event the browser uses for light-dismiss (earliest safe
|
|
1091
|
+
* moment), always fires (`click` requires same down/up target), is hard
|
|
1092
|
+
* to lose to upstream `stopPropagation`, and is uniform across input
|
|
1093
|
+
* types. Off the top-layer path we open synchronously as before.
|
|
1094
|
+
*/
|
|
1095
|
+
}, {
|
|
1096
|
+
key: "openMenuAfterPointerUp",
|
|
1097
|
+
value: function openMenuAfterPointerUp(focusOption) {
|
|
1098
|
+
var _this2 = this;
|
|
1099
|
+
if (!this.shouldDeferOpenPastPointerUp()) {
|
|
1100
|
+
this.openMenu(focusOption);
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
// A second pointerdown can land before the queued pointerup if the
|
|
1104
|
+
// user releases and re-clicks very quickly. Replace any pending
|
|
1105
|
+
// deferred open with the latest one so we never stack listeners.
|
|
1106
|
+
this.cancelDeferredOpenMenu();
|
|
1107
|
+
var handlePointerUp = function handlePointerUp() {
|
|
1108
|
+
_this2.deferredOpenMenuCleanup = null;
|
|
1109
|
+
_this2.openMenu(focusOption);
|
|
1110
|
+
};
|
|
1111
|
+
this.deferredOpenMenuCleanup = bind(document, {
|
|
1112
|
+
type: 'pointerup',
|
|
1113
|
+
listener: handlePointerUp,
|
|
1114
|
+
options: {
|
|
1115
|
+
capture: true,
|
|
1116
|
+
once: true
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
}, {
|
|
1121
|
+
key: "cancelDeferredOpenMenu",
|
|
1122
|
+
value: function cancelDeferredOpenMenu() {
|
|
1123
|
+
if (this.deferredOpenMenuCleanup) {
|
|
1124
|
+
this.deferredOpenMenuCleanup();
|
|
1125
|
+
this.deferredOpenMenuCleanup = null;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1040
1128
|
}, {
|
|
1041
1129
|
key: "openMenu",
|
|
1042
1130
|
value: function openMenu(focusOption) {
|
|
1043
|
-
var
|
|
1131
|
+
var _this3 = this;
|
|
1044
1132
|
var _this$state2 = this.state,
|
|
1045
1133
|
selectValue = _this$state2.selectValue,
|
|
1046
1134
|
isFocused = _this$state2.isFocused;
|
|
@@ -1062,25 +1150,25 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1062
1150
|
focusedOption: focusedOption,
|
|
1063
1151
|
focusedOptionId: this.getFocusedOptionId(focusedOption)
|
|
1064
1152
|
}, function () {
|
|
1065
|
-
return
|
|
1153
|
+
return _this3.onMenuOpen();
|
|
1066
1154
|
});
|
|
1067
1155
|
isSafari() && focusedOption && this.updateInputLabel(this.calculateInputLabel(focusedOption, openAtIndex));
|
|
1068
1156
|
}
|
|
1069
1157
|
}, {
|
|
1070
1158
|
key: "updateInputLabel",
|
|
1071
1159
|
value: function updateInputLabel(inputLabel) {
|
|
1072
|
-
var
|
|
1160
|
+
var _this4 = this;
|
|
1073
1161
|
if (inputLabel) {
|
|
1074
1162
|
var _this$inputRef;
|
|
1075
1163
|
(_this$inputRef = this.inputRef) === null || _this$inputRef === void 0 || _this$inputRef.setAttribute('aria-label', inputLabel);
|
|
1076
1164
|
setTimeout(function () {
|
|
1077
|
-
var normalizedLabel =
|
|
1165
|
+
var normalizedLabel = _this4.props['aria-label'] || _this4.props.label;
|
|
1078
1166
|
if (normalizedLabel) {
|
|
1079
|
-
var
|
|
1080
|
-
(
|
|
1167
|
+
var _this4$inputRef;
|
|
1168
|
+
(_this4$inputRef = _this4.inputRef) === null || _this4$inputRef === void 0 || _this4$inputRef.setAttribute('aria-label', normalizedLabel);
|
|
1081
1169
|
} else {
|
|
1082
|
-
var
|
|
1083
|
-
(
|
|
1170
|
+
var _this4$inputRef2;
|
|
1171
|
+
(_this4$inputRef2 = _this4.inputRef) === null || _this4$inputRef2 === void 0 || _this4$inputRef2.removeAttribute('aria-label');
|
|
1084
1172
|
}
|
|
1085
1173
|
}, 500);
|
|
1086
1174
|
}
|
|
@@ -1088,14 +1176,14 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1088
1176
|
}, {
|
|
1089
1177
|
key: "calculateInputLabel",
|
|
1090
1178
|
value: function calculateInputLabel(focusedOption, optionIndex) {
|
|
1091
|
-
var
|
|
1179
|
+
var _this5 = this;
|
|
1092
1180
|
var options = this.props.options;
|
|
1093
1181
|
var isOptionsGrouped = options === null || options === void 0 ? void 0 : options.every(function (obj) {
|
|
1094
1182
|
return _typeof(obj) === 'object' && obj !== null && 'options' in obj;
|
|
1095
1183
|
});
|
|
1096
1184
|
var inputLabel = this.getOptionLabel(focusedOption);
|
|
1097
1185
|
var isOptionFocused = function isOptionFocused(option) {
|
|
1098
|
-
return
|
|
1186
|
+
return _this5.getOptionLabel(option) === inputLabel;
|
|
1099
1187
|
};
|
|
1100
1188
|
var groupData = options === null || options === void 0 ? void 0 : options.find(function (option) {
|
|
1101
1189
|
var _groupCandidate$optio, _groupCandidate$optio2;
|
|
@@ -1483,7 +1571,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1483
1571
|
}, {
|
|
1484
1572
|
key: "renderPlaceholderOrValue",
|
|
1485
1573
|
value: function renderPlaceholderOrValue() {
|
|
1486
|
-
var
|
|
1574
|
+
var _this6 = this;
|
|
1487
1575
|
var _this$getComponents2 = this.getComponents(),
|
|
1488
1576
|
MultiValue = _this$getComponents2.MultiValue,
|
|
1489
1577
|
MultiValueContainer = _this$getComponents2.MultiValueContainer,
|
|
@@ -1518,7 +1606,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1518
1606
|
if (isMulti) {
|
|
1519
1607
|
return selectValue.map(function (opt, index) {
|
|
1520
1608
|
var isOptionFocused = opt === focusedValue;
|
|
1521
|
-
var key = "".concat(
|
|
1609
|
+
var key = "".concat(_this6.getOptionLabel(opt), "-").concat(_this6.getOptionValue(opt));
|
|
1522
1610
|
return /*#__PURE__*/React.createElement(MultiValue, _extends({}, commonProps, {
|
|
1523
1611
|
components: {
|
|
1524
1612
|
Container: MultiValueContainer,
|
|
@@ -1531,10 +1619,10 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1531
1619
|
index: index,
|
|
1532
1620
|
removeProps: _objectSpread(_objectSpread({
|
|
1533
1621
|
onClick: function onClick() {
|
|
1534
|
-
return
|
|
1622
|
+
return _this6.removeValue(opt);
|
|
1535
1623
|
},
|
|
1536
1624
|
onTouchEnd: function onTouchEnd() {
|
|
1537
|
-
return
|
|
1625
|
+
return _this6.removeValue(opt);
|
|
1538
1626
|
},
|
|
1539
1627
|
onMouseDown: function onMouseDown(e) {
|
|
1540
1628
|
e.preventDefault();
|
|
@@ -1542,15 +1630,15 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1542
1630
|
}, testId && {
|
|
1543
1631
|
'data-testid': "".concat(testId, "-select--multivalue-").concat(index, "-remove")
|
|
1544
1632
|
}), {}, {
|
|
1545
|
-
id: "".concat(
|
|
1633
|
+
id: "".concat(_this6.getElementId('selected-value'), "-").concat(index, "-remove")
|
|
1546
1634
|
}),
|
|
1547
1635
|
data: opt,
|
|
1548
1636
|
innerProps: _objectSpread(_objectSpread({}, testId && {
|
|
1549
1637
|
'data-testid': "".concat(testId, "-select--multivalue-").concat(index)
|
|
1550
1638
|
}), {}, {
|
|
1551
|
-
id: "".concat(
|
|
1639
|
+
id: "".concat(_this6.getElementId('selected-value'), "-").concat(index)
|
|
1552
1640
|
})
|
|
1553
|
-
}),
|
|
1641
|
+
}), _this6.formatOptionLabel(opt, 'value'));
|
|
1554
1642
|
});
|
|
1555
1643
|
}
|
|
1556
1644
|
if (inputValue) {
|
|
@@ -1656,7 +1744,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1656
1744
|
}, {
|
|
1657
1745
|
key: "renderMenu",
|
|
1658
1746
|
value: function renderMenu() {
|
|
1659
|
-
var
|
|
1747
|
+
var _this7 = this;
|
|
1660
1748
|
var _this$getComponents6 = this.getComponents(),
|
|
1661
1749
|
Group = _this$getComponents6.Group,
|
|
1662
1750
|
GroupHeading = _this$getComponents6.GroupHeading,
|
|
@@ -1699,19 +1787,19 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1699
1787
|
value = props.value;
|
|
1700
1788
|
var isFocused = focusedOption === data;
|
|
1701
1789
|
var onHover = isDisabled ? undefined : function () {
|
|
1702
|
-
return
|
|
1790
|
+
return _this7.onOptionHover(data);
|
|
1703
1791
|
};
|
|
1704
1792
|
var onSelect = isDisabled ? undefined : function () {
|
|
1705
|
-
return
|
|
1793
|
+
return _this7.selectOption(data);
|
|
1706
1794
|
};
|
|
1707
|
-
var optionId = "".concat(
|
|
1795
|
+
var optionId = "".concat(_this7.getElementId('option'), "-").concat(id);
|
|
1708
1796
|
var innerProps = _objectSpread({
|
|
1709
1797
|
id: optionId,
|
|
1710
1798
|
onClick: onSelect,
|
|
1711
1799
|
onMouseMove: onHover,
|
|
1712
1800
|
onMouseOver: onHover,
|
|
1713
|
-
role:
|
|
1714
|
-
'aria-selected':
|
|
1801
|
+
role: _this7.props['UNSAFE_is_experimental_generic'] ? 'listitem' : 'option',
|
|
1802
|
+
'aria-selected': _this7.props['UNSAFE_is_experimental_generic'] ? undefined : isSelected,
|
|
1715
1803
|
// We don't want aria-disabled if it's false. It's just noisy.
|
|
1716
1804
|
'aria-disabled': !isDisabled ? undefined : isDisabled,
|
|
1717
1805
|
'aria-describedby': headingId
|
|
@@ -1728,8 +1816,8 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1728
1816
|
type: type,
|
|
1729
1817
|
value: value,
|
|
1730
1818
|
isFocused: isFocused,
|
|
1731
|
-
innerRef: isFocused ?
|
|
1732
|
-
}),
|
|
1819
|
+
innerRef: isFocused ? _this7.getFocusedOptionRef : undefined
|
|
1820
|
+
}), _this7.formatOptionLabel(props.data, 'menu'));
|
|
1733
1821
|
};
|
|
1734
1822
|
var menuUI;
|
|
1735
1823
|
if (this.hasOptions()) {
|
|
@@ -1739,7 +1827,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1739
1827
|
var data = item.data,
|
|
1740
1828
|
options = item.options,
|
|
1741
1829
|
groupIndex = item.index;
|
|
1742
|
-
var groupId = "".concat(
|
|
1830
|
+
var groupId = "".concat(_this7.getElementId('group'), "-").concat(groupIndex);
|
|
1743
1831
|
var headingId = "".concat(groupId, "-heading");
|
|
1744
1832
|
return /*#__PURE__*/React.createElement(Group, _extends({}, commonProps, {
|
|
1745
1833
|
key: groupId,
|
|
@@ -1752,7 +1840,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1752
1840
|
}, testId && {
|
|
1753
1841
|
'data-testid': "".concat(testId, "-select--group-").concat(groupIndex, "-heading")
|
|
1754
1842
|
}),
|
|
1755
|
-
label:
|
|
1843
|
+
label: _this7.formatGroupLabel(item.data)
|
|
1756
1844
|
}), item.options.map(function (option) {
|
|
1757
1845
|
return render(option, "".concat(groupIndex, "-").concat(option.index), headingId);
|
|
1758
1846
|
}));
|
|
@@ -1799,9 +1887,9 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1799
1887
|
return /*#__PURE__*/React.createElement(Menu, _extends({}, commonProps, menuPlacementProps, {
|
|
1800
1888
|
innerRef: ref,
|
|
1801
1889
|
innerProps: _objectSpread({
|
|
1802
|
-
onMouseDown:
|
|
1803
|
-
onMouseMove:
|
|
1804
|
-
id:
|
|
1890
|
+
onMouseDown: _this7.onMenuMouseDown,
|
|
1891
|
+
onMouseMove: _this7.onMenuMouseMove,
|
|
1892
|
+
id: _this7.props.components.Menu ? _this7.getElementId('listbox') : undefined
|
|
1805
1893
|
}, testId && {
|
|
1806
1894
|
'data-testid': "".concat(testId, "-select--listbox-container")
|
|
1807
1895
|
}),
|
|
@@ -1813,47 +1901,62 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1813
1901
|
onBottomArrive: onMenuScrollToBottom,
|
|
1814
1902
|
lockEnabled: menuShouldBlockScroll
|
|
1815
1903
|
}, function (scrollTargetRef) {
|
|
1816
|
-
var
|
|
1904
|
+
var _this7$inputRef, _this7$inputRef2;
|
|
1817
1905
|
return /*#__PURE__*/React.createElement(MenuList, _extends({}, commonProps, {
|
|
1818
1906
|
innerRef: function innerRef(instance) {
|
|
1819
|
-
|
|
1907
|
+
_this7.getMenuListRef(instance);
|
|
1820
1908
|
scrollTargetRef(instance);
|
|
1821
1909
|
},
|
|
1822
1910
|
innerProps: _objectSpread(_objectSpread(_objectSpread({
|
|
1823
|
-
role:
|
|
1824
|
-
},
|
|
1825
|
-
'aria-labelledby': ((
|
|
1911
|
+
role: _this7.props['UNSAFE_is_experimental_generic'] ? 'dialog' : 'listbox'
|
|
1912
|
+
}, _this7.props['UNSAFE_is_experimental_generic'] && {
|
|
1913
|
+
'aria-labelledby': ((_this7$inputRef = _this7.inputRef) === null || _this7$inputRef === void 0 ? void 0 : _this7$inputRef.id) || _this7.getElementId('input')
|
|
1826
1914
|
}), {}, {
|
|
1827
|
-
'aria-multiselectable': !commonProps.isMulti ||
|
|
1828
|
-
id:
|
|
1915
|
+
'aria-multiselectable': !commonProps.isMulti || _this7.props['UNSAFE_is_experimental_generic'] ? undefined : commonProps.isMulti,
|
|
1916
|
+
id: _this7.getElementId('listbox')
|
|
1829
1917
|
}, testId && {
|
|
1830
1918
|
'data-testid': "".concat(testId, "-select--listbox")
|
|
1831
|
-
}), isSafari() && !
|
|
1832
|
-
'aria-describedby': ((
|
|
1919
|
+
}), isSafari() && !_this7.props['UNSAFE_is_experimental_generic'] && {
|
|
1920
|
+
'aria-describedby': ((_this7$inputRef2 = _this7.inputRef) === null || _this7$inputRef2 === void 0 ? void 0 : _this7$inputRef2.id) || _this7.getElementId('input')
|
|
1833
1921
|
}),
|
|
1834
1922
|
isLoading: isLoading,
|
|
1835
1923
|
maxHeight: maxHeight,
|
|
1836
1924
|
focusedOption: focusedOption
|
|
1837
|
-
}),
|
|
1925
|
+
}), _this7.props['UNSAFE_is_experimental_generic'] ? /*#__PURE__*/React.createElement("div", {
|
|
1838
1926
|
role: "list"
|
|
1839
1927
|
}, menuUI) : menuUI);
|
|
1840
1928
|
}));
|
|
1841
1929
|
});
|
|
1842
1930
|
|
|
1843
|
-
//
|
|
1844
|
-
//
|
|
1845
|
-
//
|
|
1846
|
-
|
|
1931
|
+
// On the top-layer path the menu always portals (into the top layer)
|
|
1932
|
+
// regardless of consumer `menuPortalTarget` / `menuPosition`. Off the
|
|
1933
|
+
// flag we keep the legacy "portal only when needed" behaviour.
|
|
1934
|
+
var shouldPortal = fg('platform-dst-top-layer') || menuPortalTarget || menuPosition === 'fixed';
|
|
1935
|
+
if (!shouldPortal) {
|
|
1936
|
+
return menuElement;
|
|
1937
|
+
}
|
|
1938
|
+
// Top-layer path needs the state mirror so MenuPortalTopLayer re-renders
|
|
1939
|
+
// when the anchor attaches; legacy path keeps the direct ref read.
|
|
1940
|
+
var controlElementForPortal = fg('platform-dst-top-layer') ? this.state.controlElement : this.controlRef;
|
|
1941
|
+
var menuPortal = /*#__PURE__*/React.createElement(MenuPortal, _extends({}, commonProps, {
|
|
1847
1942
|
appendTo: menuPortalTarget,
|
|
1848
|
-
controlElement:
|
|
1943
|
+
controlElement: controlElementForPortal,
|
|
1849
1944
|
menuPlacement: menuPlacement,
|
|
1850
1945
|
menuPosition: menuPosition
|
|
1851
|
-
}), menuElement)
|
|
1946
|
+
}), menuElement);
|
|
1947
|
+
// The Provider plumbs the close signal to MenuPortalTopLayer; not
|
|
1948
|
+
// needed on the legacy path.
|
|
1949
|
+
if (!fg('platform-dst-top-layer')) {
|
|
1950
|
+
return menuPortal;
|
|
1951
|
+
}
|
|
1952
|
+
return /*#__PURE__*/React.createElement(MenuPortalCloseContext.Provider, {
|
|
1953
|
+
value: this.handleOpenLayerObserverCloseSignal
|
|
1954
|
+
}, menuPortal);
|
|
1852
1955
|
}
|
|
1853
1956
|
}, {
|
|
1854
1957
|
key: "renderFormField",
|
|
1855
1958
|
value: function renderFormField() {
|
|
1856
|
-
var
|
|
1959
|
+
var _this8 = this;
|
|
1857
1960
|
var _this$props12 = this.props,
|
|
1858
1961
|
delimiter = _this$props12.delimiter,
|
|
1859
1962
|
isDisabled = _this$props12.isDisabled,
|
|
@@ -1873,7 +1976,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1873
1976
|
if (isMulti) {
|
|
1874
1977
|
if (delimiter) {
|
|
1875
1978
|
var value = selectValue.map(function (opt) {
|
|
1876
|
-
return
|
|
1979
|
+
return _this8.getOptionValue(opt);
|
|
1877
1980
|
}).join(delimiter);
|
|
1878
1981
|
return /*#__PURE__*/React.createElement("input", {
|
|
1879
1982
|
name: name,
|
|
@@ -1886,7 +1989,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1886
1989
|
key: "i-".concat(i),
|
|
1887
1990
|
name: name,
|
|
1888
1991
|
type: "hidden",
|
|
1889
|
-
value:
|
|
1992
|
+
value: _this8.getOptionValue(opt)
|
|
1890
1993
|
});
|
|
1891
1994
|
}) : /*#__PURE__*/React.createElement("input", {
|
|
1892
1995
|
name: name,
|
|
@@ -2001,7 +2104,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
2001
2104
|
innerProps: _objectSpread({}, testId && {
|
|
2002
2105
|
'data-testid': "".concat(testId, "-select--indicators-container")
|
|
2003
2106
|
})
|
|
2004
|
-
}), this.renderClearIndicator(), this.renderLoadingIndicator(), this.renderDropdownIndicator())), this.renderMenu(), this.renderFormField(), /*#__PURE__*/React.createElement(NotifyOpenLayerObserver, {
|
|
2107
|
+
}), this.renderClearIndicator(), this.renderLoadingIndicator(), this.renderDropdownIndicator())), this.renderMenu(), this.renderFormField(), !fg('platform-dst-top-layer') && /*#__PURE__*/React.createElement(NotifyOpenLayerObserver, {
|
|
2005
2108
|
isOpen: this.props.menuIsOpen,
|
|
2006
2109
|
onClose: this.handleOpenLayerObserverCloseSignal
|
|
2007
2110
|
})));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jsxRuntime classic
|
|
3
|
+
* @jsx jsx
|
|
4
|
+
*/
|
|
5
|
+
import { type ReactNode } from 'react';
|
|
6
|
+
import type { GroupBase } from '../types';
|
|
7
|
+
import type { MenuPortalProps } from './menu-portal';
|
|
8
|
+
/**
|
|
9
|
+
* Top-layer host for react-select's menu. Hands positioning, flip, and width
|
|
10
|
+
* to `@atlaskit/top-layer`; ignores `appendTo` / `menuPortalTarget` /
|
|
11
|
+
* `menuPosition`. The browser-dismiss handler is supplied internally by
|
|
12
|
+
* `Select` via `MenuPortalCloseContext`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <MenuPortalTopLayer controlElement={el} menuPlacement="bottom">
|
|
17
|
+
* <Menu />
|
|
18
|
+
* </MenuPortalTopLayer>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function MenuPortalTopLayer<Option, IsMulti extends boolean, Group extends GroupBase<Option>>(props: MenuPortalProps<Option, IsMulti, Group>): ReactNode;
|
|
@@ -20,4 +20,9 @@ export interface PortalStyleArgs {
|
|
|
20
20
|
width: number;
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Public-facing `MenuPortal` component. Routes between the legacy
|
|
25
|
+
* `createPortal`-based implementation and the top-layer-based
|
|
26
|
+
* `MenuPortalTopLayer` based on the `platform-dst-top-layer` feature flag.
|
|
27
|
+
*/
|
|
28
|
+
export declare const MenuPortal: <Option, IsMulti extends boolean, Group extends GroupBase<Option>>(props: MenuPortalProps<Option, IsMulti, Group>) => ReactNode;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Context } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Internal handoff from `Select` to `MenuPortalTopLayer` for the top-layer
|
|
4
|
+
* dismiss signal. Kept off `MenuPortalProps` to avoid widening the public
|
|
5
|
+
* subpath export at `@atlaskit/react-select/menu-portal`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MenuPortalCloseContext: Context<(() => void) | undefined>;
|
package/dist/types/select.d.ts
CHANGED
|
@@ -454,6 +454,14 @@ interface State<Option, IsMulti extends boolean, Group extends GroupBase<Option>
|
|
|
454
454
|
inputIsHiddenAfterUpdate: boolean | null | undefined;
|
|
455
455
|
prevProps: SelectProps<Option, IsMulti, Group> | void;
|
|
456
456
|
instancePrefix: string;
|
|
457
|
+
/**
|
|
458
|
+
* State mirror of `controlRef`, used only on the top-layer path so
|
|
459
|
+
* `MenuPortalTopLayer` re-renders when the anchor attaches (its layout
|
|
460
|
+
* effects depend on this prop). Legacy `MenuPortalLegacy` measures during
|
|
461
|
+
* render and keeps reading `controlRef` directly. Stays `null` when the
|
|
462
|
+
* `platform-dst-top-layer` flag is off.
|
|
463
|
+
*/
|
|
464
|
+
controlElement: HTMLDivElement | null;
|
|
457
465
|
}
|
|
458
466
|
interface CategorizedOption<Option> {
|
|
459
467
|
type: 'option';
|
|
@@ -485,6 +493,7 @@ export default class Select<Option = unknown, IsMulti extends boolean = false, G
|
|
|
485
493
|
initialTouchY: number;
|
|
486
494
|
openAfterFocus: boolean;
|
|
487
495
|
scrollToFocusedOptionOnUpdate: boolean;
|
|
496
|
+
deferredOpenMenuCleanup: (() => void) | null;
|
|
488
497
|
userIsDragging?: boolean;
|
|
489
498
|
controlRef: HTMLDivElement | null;
|
|
490
499
|
getControlRef: RefCallback<HTMLDivElement>;
|
|
@@ -518,6 +527,31 @@ export default class Select<Option = unknown, IsMulti extends boolean = false, G
|
|
|
518
527
|
blurInput(): void;
|
|
519
528
|
focus: () => void;
|
|
520
529
|
blur: () => void;
|
|
530
|
+
/**
|
|
531
|
+
* Whether to defer the menu open past the in-flight pointer gesture.
|
|
532
|
+
* Any renderer that drives a `popover="auto"` element must, otherwise
|
|
533
|
+
* the browser's light-dismiss runs on the matching `pointerup` and
|
|
534
|
+
* closes the menu immediately. Today only the top-layer path needs it.
|
|
535
|
+
*/
|
|
536
|
+
private shouldDeferOpenPastPointerUp;
|
|
537
|
+
/**
|
|
538
|
+
* Open the menu after the current pointer gesture, instead of
|
|
539
|
+
* synchronously inside `mousedown`.
|
|
540
|
+
*
|
|
541
|
+
* On the top-layer path the menu is a `popover="auto"` element. The
|
|
542
|
+
* browser captures the pointerdown target before the popover exists, so
|
|
543
|
+
* opening synchronously gets immediately light-dismissed on pointerup
|
|
544
|
+
* (and the matching `beforetoggle: closed` is not cancellable). Deferring
|
|
545
|
+
* to the next `pointerup` avoids that.
|
|
546
|
+
*
|
|
547
|
+
* We listen for `pointerup` rather than `click` because `pointerup` is
|
|
548
|
+
* the exact event the browser uses for light-dismiss (earliest safe
|
|
549
|
+
* moment), always fires (`click` requires same down/up target), is hard
|
|
550
|
+
* to lose to upstream `stopPropagation`, and is uniform across input
|
|
551
|
+
* types. Off the top-layer path we open synchronously as before.
|
|
552
|
+
*/
|
|
553
|
+
private openMenuAfterPointerUp;
|
|
554
|
+
cancelDeferredOpenMenu(): void;
|
|
521
555
|
openMenu(focusOption: 'first' | 'last'): void;
|
|
522
556
|
updateInputLabel(inputLabel?: string): void;
|
|
523
557
|
calculateInputLabel(focusedOption: Option, optionIndex: number): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/react-select",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "A forked version of react-select to only be used in atlaskit/select",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"atlaskit:src": "src/index.tsx",
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@atlaskit/browser-apis": "^1.0.0",
|
|
23
24
|
"@atlaskit/ds-lib": "^8.0.0",
|
|
24
25
|
"@atlaskit/icon": "^36.0.0",
|
|
25
26
|
"@atlaskit/layering": "^4.0.0",
|
|
@@ -28,9 +29,11 @@
|
|
|
28
29
|
"@atlaskit/spinner": "^20.0.0",
|
|
29
30
|
"@atlaskit/tag": "^15.0.0",
|
|
30
31
|
"@atlaskit/tokens": "^14.0.0",
|
|
32
|
+
"@atlaskit/top-layer": "^1.0.0",
|
|
31
33
|
"@babel/runtime": "^7.0.0",
|
|
32
34
|
"@compiled/react": "^0.20.0",
|
|
33
35
|
"@floating-ui/dom": "^1.0.1",
|
|
36
|
+
"bind-event-listener": "^3.0.0",
|
|
34
37
|
"memoize-one": "^6.0.0",
|
|
35
38
|
"use-isomorphic-layout-effect": "^1.1.2"
|
|
36
39
|
},
|
|
@@ -40,14 +43,19 @@
|
|
|
40
43
|
},
|
|
41
44
|
"devDependencies": {
|
|
42
45
|
"@af/accessibility-testing": "workspace:^",
|
|
46
|
+
"@af/integration-testing": "workspace:^",
|
|
47
|
+
"@af/visual-regression": "workspace:^",
|
|
48
|
+
"@atlaskit/button": "^24.1.0",
|
|
43
49
|
"@atlaskit/checkbox": "^18.0.0",
|
|
44
50
|
"@atlaskit/form": "^16.0.0",
|
|
51
|
+
"@atlaskit/modal-dialog": "^16.0.0",
|
|
45
52
|
"@atlassian/feature-flags-test-utils": "^1.1.0",
|
|
46
53
|
"@testing-library/react": "^16.3.0",
|
|
47
54
|
"@testing-library/user-event": "^14.4.3",
|
|
48
55
|
"jest-in-case": "^1.0.2",
|
|
49
56
|
"react": "^18.2.0",
|
|
50
|
-
"react-dom": "^18.2.0"
|
|
57
|
+
"react-dom": "^18.2.0",
|
|
58
|
+
"tiny-invariant": "^1.2.0"
|
|
51
59
|
},
|
|
52
60
|
"platform-feature-flags": {
|
|
53
61
|
"custom-interactive-elements-not-keyboard-focusable": {
|
|
@@ -70,6 +78,9 @@
|
|
|
70
78
|
},
|
|
71
79
|
"platform-dst-lozenge-tag-badge-visual-uplifts": {
|
|
72
80
|
"type": "boolean"
|
|
81
|
+
},
|
|
82
|
+
"platform-dst-top-layer": {
|
|
83
|
+
"type": "boolean"
|
|
73
84
|
}
|
|
74
85
|
},
|
|
75
86
|
"techstack": {
|