@atlaskit/react-select 1.0.6 → 1.2.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 +16 -0
- package/dist/cjs/accessibility/helpers.js +5 -0
- package/dist/cjs/accessibility/index.js +7 -3
- package/dist/cjs/components/live-region.js +32 -14
- package/dist/cjs/components/menu.js +3 -1
- package/dist/cjs/components/option.js +3 -1
- package/dist/cjs/select.js +26 -13
- package/dist/es2019/accessibility/helpers.js +4 -0
- package/dist/es2019/accessibility/index.js +7 -3
- package/dist/es2019/components/live-region.js +33 -15
- package/dist/es2019/components/menu.js +3 -1
- package/dist/es2019/components/option.js +3 -1
- package/dist/es2019/select.js +27 -13
- package/dist/esm/accessibility/helpers.js +4 -0
- package/dist/esm/accessibility/index.js +7 -3
- package/dist/esm/components/live-region.js +33 -15
- package/dist/esm/components/menu.js +3 -1
- package/dist/esm/components/option.js +3 -1
- package/dist/esm/select.js +27 -14
- package/dist/types/accessibility/helpers.d.ts +1 -0
- package/dist/types/accessibility/index.d.ts +4 -0
- package/dist/types/components/indicators.d.ts +4 -0
- package/dist/types/select.d.ts +4 -0
- package/dist/types-ts4.5/accessibility/helpers.d.ts +1 -0
- package/dist/types-ts4.5/accessibility/index.d.ts +4 -0
- package/dist/types-ts4.5/components/indicators.d.ts +4 -0
- package/dist/types-ts4.5/select.d.ts +4 -0
- package/package.json +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @atlaskit/react-select
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#157818](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/157818)
|
|
8
|
+
[`87c14ad1a3efa`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/87c14ad1a3efa) -
|
|
9
|
+
Use semantic tags and arias for combobox and listbox and reduce aria-live
|
|
10
|
+
|
|
11
|
+
## 1.1.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- [#156026](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/156026)
|
|
16
|
+
[`709b9c76673df`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/709b9c76673df) -
|
|
17
|
+
add clearControlLabel prop to pass aria-label to clear icon button
|
|
18
|
+
|
|
3
19
|
## 1.0.6
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
|
@@ -8,6 +8,7 @@ exports.isIOS = isIOS;
|
|
|
8
8
|
exports.isIPad = isIPad;
|
|
9
9
|
exports.isIPhone = isIPhone;
|
|
10
10
|
exports.isMac = isMac;
|
|
11
|
+
exports.isSafari = isSafari;
|
|
11
12
|
function testPlatform(re) {
|
|
12
13
|
var _window$navigator$use;
|
|
13
14
|
return typeof window !== 'undefined' && window.navigator != null ?
|
|
@@ -17,6 +18,10 @@ function testPlatform(re) {
|
|
|
17
18
|
function isIPhone() {
|
|
18
19
|
return testPlatform(/^iPhone/i);
|
|
19
20
|
}
|
|
21
|
+
function isSafari() {
|
|
22
|
+
var ua = navigator.userAgent.toLowerCase();
|
|
23
|
+
return ua.includes('safari') && !ua.includes('chrome');
|
|
24
|
+
}
|
|
20
25
|
function isMac() {
|
|
21
26
|
return testPlatform(/^Mac/i);
|
|
22
27
|
}
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.defaultAriaLiveMessages = void 0;
|
|
7
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
7
8
|
var defaultAriaLiveMessages = exports.defaultAriaLiveMessages = {
|
|
8
9
|
guidance: function guidance(props) {
|
|
9
10
|
var isSearchable = props.isSearchable,
|
|
@@ -50,19 +51,22 @@ var defaultAriaLiveMessages = exports.defaultAriaLiveMessages = {
|
|
|
50
51
|
_props$label2 = props.label,
|
|
51
52
|
label = _props$label2 === void 0 ? '' : _props$label2,
|
|
52
53
|
selectValue = props.selectValue,
|
|
54
|
+
isMulti = props.isMulti,
|
|
53
55
|
isDisabled = props.isDisabled,
|
|
54
56
|
isSelected = props.isSelected,
|
|
55
57
|
isAppleDevice = props.isAppleDevice;
|
|
56
58
|
var getArrayIndex = function getArrayIndex(arr, item) {
|
|
57
|
-
return arr && arr.length ? "".concat(arr.indexOf(item) + 1, " of ").concat(arr.length) : '';
|
|
59
|
+
return arr && arr.length ? "(".concat(arr.indexOf(item) + 1, " of ").concat(arr.length, ")") : '';
|
|
58
60
|
};
|
|
59
61
|
if (context === 'value' && selectValue) {
|
|
60
62
|
return "value ".concat(label, " focused, ").concat(getArrayIndex(selectValue, focused), ".");
|
|
61
63
|
}
|
|
62
64
|
if (context === 'menu' && isAppleDevice) {
|
|
63
65
|
var disabled = isDisabled ? ' disabled' : '';
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
// don't announce not selected for single selection
|
|
67
|
+
var notSelectedStatus = !isMulti && (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? '' : ' not selected';
|
|
68
|
+
var status = "".concat(isSelected ? ' selected' : notSelectedStatus).concat(disabled);
|
|
69
|
+
return "".concat(label).concat(status, ", ").concat(getArrayIndex(options, focused), ", completion selected");
|
|
66
70
|
}
|
|
67
71
|
return '';
|
|
68
72
|
},
|
|
@@ -8,6 +8,7 @@ exports.default = void 0;
|
|
|
8
8
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
9
|
var _react = require("react");
|
|
10
10
|
var _react2 = require("@emotion/react");
|
|
11
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
11
12
|
var _accessibility = require("../accessibility");
|
|
12
13
|
var _a11yText = _interopRequireDefault(require("./internal/a11y-text"));
|
|
13
14
|
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; }
|
|
@@ -45,6 +46,9 @@ var LiveRegion = function LiveRegion(props) {
|
|
|
45
46
|
var ariaLabel = selectProps['aria-label'] || label;
|
|
46
47
|
var ariaLive = selectProps['aria-live'];
|
|
47
48
|
|
|
49
|
+
// for safari, we will use minimum support from aria-live region
|
|
50
|
+
var isA11yImprovementEnabled = (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') && !isAppleDevice;
|
|
51
|
+
|
|
48
52
|
// Update aria live message configuration when prop changes
|
|
49
53
|
var messages = (0, _react.useMemo)(function () {
|
|
50
54
|
return _objectSpread(_objectSpread({}, _accessibility.defaultAriaLiveMessages), ariaLiveMessages || {});
|
|
@@ -82,10 +86,17 @@ var LiveRegion = function LiveRegion(props) {
|
|
|
82
86
|
}
|
|
83
87
|
return message;
|
|
84
88
|
}, [ariaSelection, messages, isOptionDisabled, selectValue, getOptionLabel]);
|
|
89
|
+
var prevInputValue = (0, _react.useRef)('');
|
|
85
90
|
var ariaFocused = (0, _react.useMemo)(function () {
|
|
86
91
|
var focusMsg = '';
|
|
87
92
|
var focused = focusedOption || focusedValue;
|
|
88
93
|
var isSelected = !!(focusedOption && selectValue && selectValue.includes(focusedOption));
|
|
94
|
+
if ((!inputValue || inputValue === prevInputValue.current) && isA11yImprovementEnabled) {
|
|
95
|
+
// only announce focus option when searching when ff is on,
|
|
96
|
+
// for safari, we will announce for all
|
|
97
|
+
return '';
|
|
98
|
+
}
|
|
99
|
+
prevInputValue.current = inputValue;
|
|
89
100
|
if (focused && messages.onFocus) {
|
|
90
101
|
var onFocusProps = {
|
|
91
102
|
focused: focused,
|
|
@@ -95,27 +106,36 @@ var LiveRegion = function LiveRegion(props) {
|
|
|
95
106
|
options: focusableOptions,
|
|
96
107
|
context: focused === focusedOption ? 'menu' : 'value',
|
|
97
108
|
selectValue: selectValue,
|
|
98
|
-
isAppleDevice: isAppleDevice
|
|
109
|
+
isAppleDevice: isAppleDevice,
|
|
110
|
+
isMulti: isMulti
|
|
99
111
|
};
|
|
100
112
|
focusMsg = messages.onFocus(onFocusProps);
|
|
101
113
|
}
|
|
102
114
|
return focusMsg;
|
|
103
|
-
}, [focusedOption, focusedValue, getOptionLabel, isOptionDisabled, messages, focusableOptions, selectValue, isAppleDevice]);
|
|
115
|
+
}, [inputValue, focusedOption, focusedValue, getOptionLabel, isOptionDisabled, messages, focusableOptions, selectValue, isAppleDevice, isA11yImprovementEnabled, isMulti]);
|
|
104
116
|
var ariaResults = (0, _react.useMemo)(function () {
|
|
105
117
|
var resultsMsg = '';
|
|
106
118
|
if (menuIsOpen && options.length && !isLoading && messages.onFilter) {
|
|
107
|
-
var
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
var shouldAnnounceAvailableResults = !focusableOptions.length;
|
|
120
|
+
if (shouldAnnounceAvailableResults && (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') || !(0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement')) {
|
|
121
|
+
// only announce no option results when ff is on
|
|
122
|
+
var resultsMessage = screenReaderStatus({
|
|
123
|
+
count: focusableOptions.length
|
|
124
|
+
});
|
|
125
|
+
resultsMsg = messages.onFilter({
|
|
126
|
+
inputValue: inputValue,
|
|
127
|
+
resultsMessage: resultsMessage
|
|
128
|
+
});
|
|
129
|
+
}
|
|
114
130
|
}
|
|
115
131
|
return resultsMsg;
|
|
116
132
|
}, [focusableOptions, inputValue, menuIsOpen, messages, options, screenReaderStatus, isLoading]);
|
|
117
133
|
var isInitialFocus = (ariaSelection === null || ariaSelection === void 0 ? void 0 : ariaSelection.action) === 'initial-input-focus';
|
|
118
134
|
var ariaGuidance = (0, _react.useMemo)(function () {
|
|
135
|
+
if ((0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement')) {
|
|
136
|
+
// don't announce guidance at all when ff is on
|
|
137
|
+
return '';
|
|
138
|
+
}
|
|
119
139
|
var guidanceMsg = '';
|
|
120
140
|
if (messages.guidance) {
|
|
121
141
|
var context = focusedValue ? 'value' : menuIsOpen ? 'menu' : 'input';
|
|
@@ -137,16 +157,14 @@ var LiveRegion = function LiveRegion(props) {
|
|
|
137
157
|
id: "aria-focused"
|
|
138
158
|
}, ariaFocused), (0, _react2.jsx)("span", {
|
|
139
159
|
id: "aria-results"
|
|
140
|
-
}, ariaResults), (0, _react2.jsx)("span", {
|
|
160
|
+
}, ariaResults), !(0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') && (0, _react2.jsx)("span", {
|
|
141
161
|
id: "aria-guidance"
|
|
142
162
|
}, ariaGuidance));
|
|
143
163
|
return (0, _react2.jsx)(_react.Fragment, null, (0, _react2.jsx)(_a11yText.default, {
|
|
144
164
|
id: id
|
|
145
165
|
}, isInitialFocus && ScreenReaderText), (0, _react2.jsx)(_a11yText.default, {
|
|
146
|
-
"aria-live": ariaLive,
|
|
147
|
-
|
|
148
|
-
"aria-relevant": "additions text",
|
|
149
|
-
role: "log"
|
|
166
|
+
"aria-live": isA11yImprovementEnabled ? 'polite' : ariaLive,
|
|
167
|
+
role: isA11yImprovementEnabled ? 'status' : 'log'
|
|
150
168
|
}, isFocused && !isInitialFocus && ScreenReaderText));
|
|
151
169
|
};
|
|
152
170
|
|
|
@@ -311,7 +311,9 @@ var MenuList = exports.MenuList = function MenuList(props) {
|
|
|
311
311
|
'menu-list--is-multi': isMulti
|
|
312
312
|
}), {
|
|
313
313
|
ref: innerRef
|
|
314
|
-
}, innerProps
|
|
314
|
+
}, innerProps, {
|
|
315
|
+
tabIndex: -1
|
|
316
|
+
}), children);
|
|
315
317
|
};
|
|
316
318
|
|
|
317
319
|
// ==============================
|
package/dist/cjs/select.js
CHANGED
|
@@ -16,6 +16,7 @@ var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/ge
|
|
|
16
16
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
17
17
|
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
18
18
|
var _react = _interopRequireWildcard(require("react"));
|
|
19
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
19
20
|
var _helpers = require("./accessibility/helpers");
|
|
20
21
|
var _builtins = require("./builtins");
|
|
21
22
|
var _components = require("./components");
|
|
@@ -258,7 +259,7 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
|
|
|
258
259
|
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "initialTouchY", 0);
|
|
259
260
|
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "openAfterFocus", false);
|
|
260
261
|
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "scrollToFocusedOptionOnUpdate", false);
|
|
261
|
-
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "isAppleDevice", (0, _helpers.isAppleDevice)());
|
|
262
|
+
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "isAppleDevice", (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? (0, _helpers.isSafari)() : (0, _helpers.isAppleDevice)());
|
|
262
263
|
// Refs
|
|
263
264
|
// ------------------------------
|
|
264
265
|
(0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "controlRef", null);
|
|
@@ -1237,7 +1238,8 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
|
|
|
1237
1238
|
labelId = _this$props8.labelId,
|
|
1238
1239
|
menuIsOpen = _this$props8.menuIsOpen,
|
|
1239
1240
|
required = _this$props8.required,
|
|
1240
|
-
tabIndex = _this$props8.tabIndex
|
|
1241
|
+
_this$props8$tabIndex = _this$props8.tabIndex,
|
|
1242
|
+
tabIndex = _this$props8$tabIndex === void 0 ? 0 : _this$props8$tabIndex;
|
|
1241
1243
|
var _this$getComponents = this.getComponents(),
|
|
1242
1244
|
Input = _this$getComponents.Input;
|
|
1243
1245
|
var _this$state4 = this.state,
|
|
@@ -1249,7 +1251,7 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
|
|
|
1249
1251
|
|
|
1250
1252
|
// aria attributes makes the JSX "noisy", separated for clarity
|
|
1251
1253
|
var ariaAttributes = _objectSpread(_objectSpread(_objectSpread({
|
|
1252
|
-
'aria-autocomplete': '
|
|
1254
|
+
'aria-autocomplete': 'both',
|
|
1253
1255
|
'aria-errormessage': this.props['aria-errormessage'],
|
|
1254
1256
|
'aria-expanded': menuIsOpen,
|
|
1255
1257
|
'aria-haspopup': 'listbox',
|
|
@@ -1259,7 +1261,7 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
|
|
|
1259
1261
|
'aria-labelledby': this.props['aria-labelledby'] || labelId,
|
|
1260
1262
|
'aria-required': required || isRequired,
|
|
1261
1263
|
role: 'combobox',
|
|
1262
|
-
'aria-activedescendant': this.
|
|
1264
|
+
'aria-activedescendant': this.state.focusedOptionId || undefined
|
|
1263
1265
|
}, menuIsOpen && {
|
|
1264
1266
|
'aria-controls': this.getElementId('listbox')
|
|
1265
1267
|
}), !isSearchable && {
|
|
@@ -1379,6 +1381,7 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
|
|
|
1379
1381
|
ClearIndicator = _this$getComponents3.ClearIndicator;
|
|
1380
1382
|
var commonProps = this.commonProps;
|
|
1381
1383
|
var _this$props10 = this.props,
|
|
1384
|
+
clearControlLabel = _this$props10.clearControlLabel,
|
|
1382
1385
|
isDisabled = _this$props10.isDisabled,
|
|
1383
1386
|
isLoading = _this$props10.isLoading;
|
|
1384
1387
|
var isFocused = this.state.isFocused;
|
|
@@ -1390,7 +1393,9 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
|
|
|
1390
1393
|
onTouchEnd: this.onClearIndicatorTouchEnd,
|
|
1391
1394
|
'aria-hidden': 'true'
|
|
1392
1395
|
};
|
|
1393
|
-
return /*#__PURE__*/_react.default.createElement(ClearIndicator, (0, _extends2.default)({
|
|
1396
|
+
return /*#__PURE__*/_react.default.createElement(ClearIndicator, (0, _extends2.default)({
|
|
1397
|
+
clearControlLabel: clearControlLabel
|
|
1398
|
+
}, commonProps, {
|
|
1394
1399
|
innerProps: innerProps,
|
|
1395
1400
|
isFocused: isFocused
|
|
1396
1401
|
}));
|
|
@@ -1488,13 +1493,15 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
|
|
|
1488
1493
|
menuShouldScrollIntoView = _this$props12.menuShouldScrollIntoView,
|
|
1489
1494
|
noOptionsMessage = _this$props12.noOptionsMessage,
|
|
1490
1495
|
onMenuScrollToTop = _this$props12.onMenuScrollToTop,
|
|
1491
|
-
onMenuScrollToBottom = _this$props12.onMenuScrollToBottom
|
|
1496
|
+
onMenuScrollToBottom = _this$props12.onMenuScrollToBottom,
|
|
1497
|
+
labelId = _this$props12.labelId,
|
|
1498
|
+
label = _this$props12.label;
|
|
1492
1499
|
if (!menuIsOpen) {
|
|
1493
1500
|
return null;
|
|
1494
1501
|
}
|
|
1495
1502
|
|
|
1496
1503
|
// TODO: Internal Option Type here
|
|
1497
|
-
var render = function render(props, id) {
|
|
1504
|
+
var render = function render(props, id, headingId) {
|
|
1498
1505
|
var type = props.type,
|
|
1499
1506
|
data = props.data,
|
|
1500
1507
|
isDisabled = props.isDisabled,
|
|
@@ -1515,7 +1522,8 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
|
|
|
1515
1522
|
onMouseMove: onHover,
|
|
1516
1523
|
onMouseOver: onHover,
|
|
1517
1524
|
role: 'option',
|
|
1518
|
-
'aria-selected':
|
|
1525
|
+
'aria-selected': !commonProps.isMulti && (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? isSelected || undefined : isSelected,
|
|
1526
|
+
'aria-describedby': (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? headingId : undefined
|
|
1519
1527
|
};
|
|
1520
1528
|
return /*#__PURE__*/_react.default.createElement(Option, (0, _extends2.default)({}, commonProps, {
|
|
1521
1529
|
innerProps: innerProps,
|
|
@@ -1532,7 +1540,8 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
|
|
|
1532
1540
|
};
|
|
1533
1541
|
var menuUI;
|
|
1534
1542
|
if (this.hasOptions()) {
|
|
1535
|
-
|
|
1543
|
+
var items = this.getCategorizedOptions();
|
|
1544
|
+
menuUI = items.map(function (item) {
|
|
1536
1545
|
if (item.type === 'group') {
|
|
1537
1546
|
var data = item.data,
|
|
1538
1547
|
options = item.options,
|
|
@@ -1550,7 +1559,7 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
|
|
|
1550
1559
|
},
|
|
1551
1560
|
label: _this4.formatGroupLabel(item.data)
|
|
1552
1561
|
}), item.options.map(function (option) {
|
|
1553
|
-
return render(option, "".concat(groupIndex, "-").concat(option.index));
|
|
1562
|
+
return render(option, "".concat(groupIndex, "-").concat(option.index), headingId);
|
|
1554
1563
|
}));
|
|
1555
1564
|
} else if (item.type === 'option') {
|
|
1556
1565
|
return render(item, "".concat(item.index));
|
|
@@ -1605,11 +1614,15 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
|
|
|
1605
1614
|
_this4.getMenuListRef(instance);
|
|
1606
1615
|
scrollTargetRef(instance);
|
|
1607
1616
|
},
|
|
1608
|
-
innerProps: {
|
|
1617
|
+
innerProps: _objectSpread({
|
|
1609
1618
|
role: 'listbox',
|
|
1610
|
-
'aria-multiselectable
|
|
1619
|
+
// don't add aria-multiselectable when ff is on and the value is false
|
|
1620
|
+
'aria-multiselectable': (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? commonProps.isMulti || undefined : commonProps.isMulti,
|
|
1611
1621
|
id: _this4.getElementId('listbox')
|
|
1612
|
-
},
|
|
1622
|
+
}, (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') && {
|
|
1623
|
+
'aria-label': label,
|
|
1624
|
+
'aria-labelledby': labelId
|
|
1625
|
+
}),
|
|
1613
1626
|
isLoading: isLoading,
|
|
1614
1627
|
maxHeight: maxHeight,
|
|
1615
1628
|
focusedOption: focusedOption
|
|
@@ -7,6 +7,10 @@ function testPlatform(re) {
|
|
|
7
7
|
export function isIPhone() {
|
|
8
8
|
return testPlatform(/^iPhone/i);
|
|
9
9
|
}
|
|
10
|
+
export function isSafari() {
|
|
11
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
12
|
+
return ua.includes('safari') && !ua.includes('chrome');
|
|
13
|
+
}
|
|
10
14
|
export function isMac() {
|
|
11
15
|
return testPlatform(/^Mac/i);
|
|
12
16
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
1
2
|
export const defaultAriaLiveMessages = {
|
|
2
3
|
guidance: props => {
|
|
3
4
|
const {
|
|
@@ -47,18 +48,21 @@ export const defaultAriaLiveMessages = {
|
|
|
47
48
|
options,
|
|
48
49
|
label = '',
|
|
49
50
|
selectValue,
|
|
51
|
+
isMulti,
|
|
50
52
|
isDisabled,
|
|
51
53
|
isSelected,
|
|
52
54
|
isAppleDevice
|
|
53
55
|
} = props;
|
|
54
|
-
const getArrayIndex = (arr, item) => arr && arr.length ?
|
|
56
|
+
const getArrayIndex = (arr, item) => arr && arr.length ? `(${arr.indexOf(item) + 1} of ${arr.length})` : '';
|
|
55
57
|
if (context === 'value' && selectValue) {
|
|
56
58
|
return `value ${label} focused, ${getArrayIndex(selectValue, focused)}.`;
|
|
57
59
|
}
|
|
58
60
|
if (context === 'menu' && isAppleDevice) {
|
|
59
61
|
const disabled = isDisabled ? ' disabled' : '';
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
// don't announce not selected for single selection
|
|
63
|
+
const notSelectedStatus = !isMulti && fg('design_system_select-a11y-improvement') ? '' : ' not selected';
|
|
64
|
+
const status = `${isSelected ? ' selected' : notSelectedStatus}${disabled}`;
|
|
65
|
+
return `${label}${status}, ${getArrayIndex(options, focused)}, completion selected`;
|
|
62
66
|
}
|
|
63
67
|
return '';
|
|
64
68
|
},
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* @jsxRuntime classic
|
|
3
3
|
* @jsx jsx
|
|
4
4
|
*/
|
|
5
|
-
import { Fragment, useMemo } from 'react';
|
|
5
|
+
import { Fragment, useMemo, useRef } from 'react';
|
|
6
6
|
import { jsx } from '@emotion/react';
|
|
7
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
7
8
|
import { defaultAriaLiveMessages } from '../accessibility';
|
|
8
9
|
import A11yText from './internal/a11y-text';
|
|
9
10
|
|
|
@@ -41,6 +42,9 @@ const LiveRegion = props => {
|
|
|
41
42
|
const ariaLabel = selectProps['aria-label'] || label;
|
|
42
43
|
const ariaLive = selectProps['aria-live'];
|
|
43
44
|
|
|
45
|
+
// for safari, we will use minimum support from aria-live region
|
|
46
|
+
const isA11yImprovementEnabled = fg('design_system_select-a11y-improvement') && !isAppleDevice;
|
|
47
|
+
|
|
44
48
|
// Update aria live message configuration when prop changes
|
|
45
49
|
const messages = useMemo(() => ({
|
|
46
50
|
...defaultAriaLiveMessages,
|
|
@@ -80,10 +84,17 @@ const LiveRegion = props => {
|
|
|
80
84
|
}
|
|
81
85
|
return message;
|
|
82
86
|
}, [ariaSelection, messages, isOptionDisabled, selectValue, getOptionLabel]);
|
|
87
|
+
const prevInputValue = useRef('');
|
|
83
88
|
const ariaFocused = useMemo(() => {
|
|
84
89
|
let focusMsg = '';
|
|
85
90
|
const focused = focusedOption || focusedValue;
|
|
86
91
|
const isSelected = !!(focusedOption && selectValue && selectValue.includes(focusedOption));
|
|
92
|
+
if ((!inputValue || inputValue === prevInputValue.current) && isA11yImprovementEnabled) {
|
|
93
|
+
// only announce focus option when searching when ff is on,
|
|
94
|
+
// for safari, we will announce for all
|
|
95
|
+
return '';
|
|
96
|
+
}
|
|
97
|
+
prevInputValue.current = inputValue;
|
|
87
98
|
if (focused && messages.onFocus) {
|
|
88
99
|
const onFocusProps = {
|
|
89
100
|
focused,
|
|
@@ -93,27 +104,36 @@ const LiveRegion = props => {
|
|
|
93
104
|
options: focusableOptions,
|
|
94
105
|
context: focused === focusedOption ? 'menu' : 'value',
|
|
95
106
|
selectValue,
|
|
96
|
-
isAppleDevice
|
|
107
|
+
isAppleDevice,
|
|
108
|
+
isMulti
|
|
97
109
|
};
|
|
98
110
|
focusMsg = messages.onFocus(onFocusProps);
|
|
99
111
|
}
|
|
100
112
|
return focusMsg;
|
|
101
|
-
}, [focusedOption, focusedValue, getOptionLabel, isOptionDisabled, messages, focusableOptions, selectValue, isAppleDevice]);
|
|
113
|
+
}, [inputValue, focusedOption, focusedValue, getOptionLabel, isOptionDisabled, messages, focusableOptions, selectValue, isAppleDevice, isA11yImprovementEnabled, isMulti]);
|
|
102
114
|
const ariaResults = useMemo(() => {
|
|
103
115
|
let resultsMsg = '';
|
|
104
116
|
if (menuIsOpen && options.length && !isLoading && messages.onFilter) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
const shouldAnnounceAvailableResults = !focusableOptions.length;
|
|
118
|
+
if (shouldAnnounceAvailableResults && fg('design_system_select-a11y-improvement') || !fg('design_system_select-a11y-improvement')) {
|
|
119
|
+
// only announce no option results when ff is on
|
|
120
|
+
const resultsMessage = screenReaderStatus({
|
|
121
|
+
count: focusableOptions.length
|
|
122
|
+
});
|
|
123
|
+
resultsMsg = messages.onFilter({
|
|
124
|
+
inputValue,
|
|
125
|
+
resultsMessage
|
|
126
|
+
});
|
|
127
|
+
}
|
|
112
128
|
}
|
|
113
129
|
return resultsMsg;
|
|
114
130
|
}, [focusableOptions, inputValue, menuIsOpen, messages, options, screenReaderStatus, isLoading]);
|
|
115
131
|
const isInitialFocus = (ariaSelection === null || ariaSelection === void 0 ? void 0 : ariaSelection.action) === 'initial-input-focus';
|
|
116
132
|
const ariaGuidance = useMemo(() => {
|
|
133
|
+
if (fg('design_system_select-a11y-improvement')) {
|
|
134
|
+
// don't announce guidance at all when ff is on
|
|
135
|
+
return '';
|
|
136
|
+
}
|
|
117
137
|
let guidanceMsg = '';
|
|
118
138
|
if (messages.guidance) {
|
|
119
139
|
const context = focusedValue ? 'value' : menuIsOpen ? 'menu' : 'input';
|
|
@@ -135,16 +155,14 @@ const LiveRegion = props => {
|
|
|
135
155
|
id: "aria-focused"
|
|
136
156
|
}, ariaFocused), jsx("span", {
|
|
137
157
|
id: "aria-results"
|
|
138
|
-
}, ariaResults), jsx("span", {
|
|
158
|
+
}, ariaResults), !fg('design_system_select-a11y-improvement') && jsx("span", {
|
|
139
159
|
id: "aria-guidance"
|
|
140
160
|
}, ariaGuidance));
|
|
141
161
|
return jsx(Fragment, null, jsx(A11yText, {
|
|
142
162
|
id: id
|
|
143
163
|
}, isInitialFocus && ScreenReaderText), jsx(A11yText, {
|
|
144
|
-
"aria-live": ariaLive,
|
|
145
|
-
|
|
146
|
-
"aria-relevant": "additions text",
|
|
147
|
-
role: "log"
|
|
164
|
+
"aria-live": isA11yImprovementEnabled ? 'polite' : ariaLive,
|
|
165
|
+
role: isA11yImprovementEnabled ? 'status' : 'log'
|
|
148
166
|
}, isFocused && !isInitialFocus && ScreenReaderText));
|
|
149
167
|
};
|
|
150
168
|
|
package/dist/es2019/select.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/extends";
|
|
2
2
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
3
3
|
import React, { Component } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
|
+
import { isAppleDevice, isSafari } from './accessibility/helpers';
|
|
5
6
|
import { formatGroupLabel as formatGroupLabelBuiltin, getOptionLabel as getOptionLabelBuiltin, getOptionValue as getOptionValueBuiltin, isOptionDisabled as isOptionDisabledBuiltin } from './builtins';
|
|
6
7
|
import { defaultComponents } from './components';
|
|
7
8
|
import { DummyInput, RequiredInput, ScrollManager } from './components/internal';
|
|
@@ -225,7 +226,7 @@ export default class Select extends Component {
|
|
|
225
226
|
_defineProperty(this, "initialTouchY", 0);
|
|
226
227
|
_defineProperty(this, "openAfterFocus", false);
|
|
227
228
|
_defineProperty(this, "scrollToFocusedOptionOnUpdate", false);
|
|
228
|
-
_defineProperty(this, "isAppleDevice", isAppleDevice());
|
|
229
|
+
_defineProperty(this, "isAppleDevice", fg('design_system_select-a11y-improvement') ? isSafari() : isAppleDevice());
|
|
229
230
|
// Refs
|
|
230
231
|
// ------------------------------
|
|
231
232
|
_defineProperty(this, "controlRef", null);
|
|
@@ -1241,7 +1242,7 @@ export default class Select extends Component {
|
|
|
1241
1242
|
labelId,
|
|
1242
1243
|
menuIsOpen,
|
|
1243
1244
|
required,
|
|
1244
|
-
tabIndex
|
|
1245
|
+
tabIndex = 0
|
|
1245
1246
|
} = this.props;
|
|
1246
1247
|
const {
|
|
1247
1248
|
Input
|
|
@@ -1258,7 +1259,7 @@ export default class Select extends Component {
|
|
|
1258
1259
|
|
|
1259
1260
|
// aria attributes makes the JSX "noisy", separated for clarity
|
|
1260
1261
|
const ariaAttributes = {
|
|
1261
|
-
'aria-autocomplete': '
|
|
1262
|
+
'aria-autocomplete': 'both',
|
|
1262
1263
|
'aria-errormessage': this.props['aria-errormessage'],
|
|
1263
1264
|
'aria-expanded': menuIsOpen,
|
|
1264
1265
|
'aria-haspopup': 'listbox',
|
|
@@ -1268,7 +1269,7 @@ export default class Select extends Component {
|
|
|
1268
1269
|
'aria-labelledby': this.props['aria-labelledby'] || labelId,
|
|
1269
1270
|
'aria-required': required || isRequired,
|
|
1270
1271
|
role: 'combobox',
|
|
1271
|
-
'aria-activedescendant': this.
|
|
1272
|
+
'aria-activedescendant': this.state.focusedOptionId || undefined,
|
|
1272
1273
|
...(menuIsOpen && {
|
|
1273
1274
|
'aria-controls': this.getElementId('listbox')
|
|
1274
1275
|
}),
|
|
@@ -1390,6 +1391,7 @@ export default class Select extends Component {
|
|
|
1390
1391
|
commonProps
|
|
1391
1392
|
} = this;
|
|
1392
1393
|
const {
|
|
1394
|
+
clearControlLabel,
|
|
1393
1395
|
isDisabled,
|
|
1394
1396
|
isLoading
|
|
1395
1397
|
} = this.props;
|
|
@@ -1404,7 +1406,9 @@ export default class Select extends Component {
|
|
|
1404
1406
|
onTouchEnd: this.onClearIndicatorTouchEnd,
|
|
1405
1407
|
'aria-hidden': 'true'
|
|
1406
1408
|
};
|
|
1407
|
-
return /*#__PURE__*/React.createElement(ClearIndicator, _extends({
|
|
1409
|
+
return /*#__PURE__*/React.createElement(ClearIndicator, _extends({
|
|
1410
|
+
clearControlLabel: clearControlLabel
|
|
1411
|
+
}, commonProps, {
|
|
1408
1412
|
innerProps: innerProps,
|
|
1409
1413
|
isFocused: isFocused
|
|
1410
1414
|
}));
|
|
@@ -1518,14 +1522,16 @@ export default class Select extends Component {
|
|
|
1518
1522
|
menuShouldScrollIntoView,
|
|
1519
1523
|
noOptionsMessage,
|
|
1520
1524
|
onMenuScrollToTop,
|
|
1521
|
-
onMenuScrollToBottom
|
|
1525
|
+
onMenuScrollToBottom,
|
|
1526
|
+
labelId,
|
|
1527
|
+
label
|
|
1522
1528
|
} = this.props;
|
|
1523
1529
|
if (!menuIsOpen) {
|
|
1524
1530
|
return null;
|
|
1525
1531
|
}
|
|
1526
1532
|
|
|
1527
1533
|
// TODO: Internal Option Type here
|
|
1528
|
-
const render = (props, id) => {
|
|
1534
|
+
const render = (props, id, headingId) => {
|
|
1529
1535
|
const {
|
|
1530
1536
|
type,
|
|
1531
1537
|
data,
|
|
@@ -1544,7 +1550,8 @@ export default class Select extends Component {
|
|
|
1544
1550
|
onMouseMove: onHover,
|
|
1545
1551
|
onMouseOver: onHover,
|
|
1546
1552
|
role: 'option',
|
|
1547
|
-
'aria-selected':
|
|
1553
|
+
'aria-selected': !commonProps.isMulti && fg('design_system_select-a11y-improvement') ? isSelected || undefined : isSelected,
|
|
1554
|
+
'aria-describedby': fg('design_system_select-a11y-improvement') ? headingId : undefined
|
|
1548
1555
|
};
|
|
1549
1556
|
return /*#__PURE__*/React.createElement(Option, _extends({}, commonProps, {
|
|
1550
1557
|
innerProps: innerProps,
|
|
@@ -1561,7 +1568,8 @@ export default class Select extends Component {
|
|
|
1561
1568
|
};
|
|
1562
1569
|
let menuUI;
|
|
1563
1570
|
if (this.hasOptions()) {
|
|
1564
|
-
|
|
1571
|
+
const items = this.getCategorizedOptions();
|
|
1572
|
+
menuUI = items.map(item => {
|
|
1565
1573
|
if (item.type === 'group') {
|
|
1566
1574
|
const {
|
|
1567
1575
|
data,
|
|
@@ -1580,7 +1588,7 @@ export default class Select extends Component {
|
|
|
1580
1588
|
data: item.data
|
|
1581
1589
|
},
|
|
1582
1590
|
label: this.formatGroupLabel(item.data)
|
|
1583
|
-
}), item.options.map(option => render(option, `${groupIndex}-${option.index}
|
|
1591
|
+
}), item.options.map(option => render(option, `${groupIndex}-${option.index}`, headingId)));
|
|
1584
1592
|
} else if (item.type === 'option') {
|
|
1585
1593
|
return render(item, `${item.index}`);
|
|
1586
1594
|
}
|
|
@@ -1636,8 +1644,14 @@ export default class Select extends Component {
|
|
|
1636
1644
|
},
|
|
1637
1645
|
innerProps: {
|
|
1638
1646
|
role: 'listbox',
|
|
1639
|
-
'aria-multiselectable
|
|
1640
|
-
|
|
1647
|
+
// don't add aria-multiselectable when ff is on and the value is false
|
|
1648
|
+
'aria-multiselectable': fg('design_system_select-a11y-improvement') ? commonProps.isMulti || undefined : commonProps.isMulti,
|
|
1649
|
+
id: this.getElementId('listbox'),
|
|
1650
|
+
// add aditional label on listbox when ff is on
|
|
1651
|
+
...(fg('design_system_select-a11y-improvement') && {
|
|
1652
|
+
'aria-label': label,
|
|
1653
|
+
'aria-labelledby': labelId
|
|
1654
|
+
})
|
|
1641
1655
|
},
|
|
1642
1656
|
isLoading: isLoading,
|
|
1643
1657
|
maxHeight: maxHeight,
|
|
@@ -7,6 +7,10 @@ function testPlatform(re) {
|
|
|
7
7
|
export function isIPhone() {
|
|
8
8
|
return testPlatform(/^iPhone/i);
|
|
9
9
|
}
|
|
10
|
+
export function isSafari() {
|
|
11
|
+
var ua = navigator.userAgent.toLowerCase();
|
|
12
|
+
return ua.includes('safari') && !ua.includes('chrome');
|
|
13
|
+
}
|
|
10
14
|
export function isMac() {
|
|
11
15
|
return testPlatform(/^Mac/i);
|
|
12
16
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
1
2
|
export var defaultAriaLiveMessages = {
|
|
2
3
|
guidance: function guidance(props) {
|
|
3
4
|
var isSearchable = props.isSearchable,
|
|
@@ -44,19 +45,22 @@ export var defaultAriaLiveMessages = {
|
|
|
44
45
|
_props$label2 = props.label,
|
|
45
46
|
label = _props$label2 === void 0 ? '' : _props$label2,
|
|
46
47
|
selectValue = props.selectValue,
|
|
48
|
+
isMulti = props.isMulti,
|
|
47
49
|
isDisabled = props.isDisabled,
|
|
48
50
|
isSelected = props.isSelected,
|
|
49
51
|
isAppleDevice = props.isAppleDevice;
|
|
50
52
|
var getArrayIndex = function getArrayIndex(arr, item) {
|
|
51
|
-
return arr && arr.length ? "".concat(arr.indexOf(item) + 1, " of ").concat(arr.length) : '';
|
|
53
|
+
return arr && arr.length ? "(".concat(arr.indexOf(item) + 1, " of ").concat(arr.length, ")") : '';
|
|
52
54
|
};
|
|
53
55
|
if (context === 'value' && selectValue) {
|
|
54
56
|
return "value ".concat(label, " focused, ").concat(getArrayIndex(selectValue, focused), ".");
|
|
55
57
|
}
|
|
56
58
|
if (context === 'menu' && isAppleDevice) {
|
|
57
59
|
var disabled = isDisabled ? ' disabled' : '';
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
// don't announce not selected for single selection
|
|
61
|
+
var notSelectedStatus = !isMulti && fg('design_system_select-a11y-improvement') ? '' : ' not selected';
|
|
62
|
+
var status = "".concat(isSelected ? ' selected' : notSelectedStatus).concat(disabled);
|
|
63
|
+
return "".concat(label).concat(status, ", ").concat(getArrayIndex(options, focused), ", completion selected");
|
|
60
64
|
}
|
|
61
65
|
return '';
|
|
62
66
|
},
|
|
@@ -5,8 +5,9 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
5
5
|
* @jsxRuntime classic
|
|
6
6
|
* @jsx jsx
|
|
7
7
|
*/
|
|
8
|
-
import { Fragment, useMemo } from 'react';
|
|
8
|
+
import { Fragment, useMemo, useRef } from 'react';
|
|
9
9
|
import { jsx } from '@emotion/react';
|
|
10
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
10
11
|
import { defaultAriaLiveMessages } from '../accessibility';
|
|
11
12
|
import A11yText from './internal/a11y-text';
|
|
12
13
|
|
|
@@ -40,6 +41,9 @@ var LiveRegion = function LiveRegion(props) {
|
|
|
40
41
|
var ariaLabel = selectProps['aria-label'] || label;
|
|
41
42
|
var ariaLive = selectProps['aria-live'];
|
|
42
43
|
|
|
44
|
+
// for safari, we will use minimum support from aria-live region
|
|
45
|
+
var isA11yImprovementEnabled = fg('design_system_select-a11y-improvement') && !isAppleDevice;
|
|
46
|
+
|
|
43
47
|
// Update aria live message configuration when prop changes
|
|
44
48
|
var messages = useMemo(function () {
|
|
45
49
|
return _objectSpread(_objectSpread({}, defaultAriaLiveMessages), ariaLiveMessages || {});
|
|
@@ -77,10 +81,17 @@ var LiveRegion = function LiveRegion(props) {
|
|
|
77
81
|
}
|
|
78
82
|
return message;
|
|
79
83
|
}, [ariaSelection, messages, isOptionDisabled, selectValue, getOptionLabel]);
|
|
84
|
+
var prevInputValue = useRef('');
|
|
80
85
|
var ariaFocused = useMemo(function () {
|
|
81
86
|
var focusMsg = '';
|
|
82
87
|
var focused = focusedOption || focusedValue;
|
|
83
88
|
var isSelected = !!(focusedOption && selectValue && selectValue.includes(focusedOption));
|
|
89
|
+
if ((!inputValue || inputValue === prevInputValue.current) && isA11yImprovementEnabled) {
|
|
90
|
+
// only announce focus option when searching when ff is on,
|
|
91
|
+
// for safari, we will announce for all
|
|
92
|
+
return '';
|
|
93
|
+
}
|
|
94
|
+
prevInputValue.current = inputValue;
|
|
84
95
|
if (focused && messages.onFocus) {
|
|
85
96
|
var onFocusProps = {
|
|
86
97
|
focused: focused,
|
|
@@ -90,27 +101,36 @@ var LiveRegion = function LiveRegion(props) {
|
|
|
90
101
|
options: focusableOptions,
|
|
91
102
|
context: focused === focusedOption ? 'menu' : 'value',
|
|
92
103
|
selectValue: selectValue,
|
|
93
|
-
isAppleDevice: isAppleDevice
|
|
104
|
+
isAppleDevice: isAppleDevice,
|
|
105
|
+
isMulti: isMulti
|
|
94
106
|
};
|
|
95
107
|
focusMsg = messages.onFocus(onFocusProps);
|
|
96
108
|
}
|
|
97
109
|
return focusMsg;
|
|
98
|
-
}, [focusedOption, focusedValue, getOptionLabel, isOptionDisabled, messages, focusableOptions, selectValue, isAppleDevice]);
|
|
110
|
+
}, [inputValue, focusedOption, focusedValue, getOptionLabel, isOptionDisabled, messages, focusableOptions, selectValue, isAppleDevice, isA11yImprovementEnabled, isMulti]);
|
|
99
111
|
var ariaResults = useMemo(function () {
|
|
100
112
|
var resultsMsg = '';
|
|
101
113
|
if (menuIsOpen && options.length && !isLoading && messages.onFilter) {
|
|
102
|
-
var
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
var shouldAnnounceAvailableResults = !focusableOptions.length;
|
|
115
|
+
if (shouldAnnounceAvailableResults && fg('design_system_select-a11y-improvement') || !fg('design_system_select-a11y-improvement')) {
|
|
116
|
+
// only announce no option results when ff is on
|
|
117
|
+
var resultsMessage = screenReaderStatus({
|
|
118
|
+
count: focusableOptions.length
|
|
119
|
+
});
|
|
120
|
+
resultsMsg = messages.onFilter({
|
|
121
|
+
inputValue: inputValue,
|
|
122
|
+
resultsMessage: resultsMessage
|
|
123
|
+
});
|
|
124
|
+
}
|
|
109
125
|
}
|
|
110
126
|
return resultsMsg;
|
|
111
127
|
}, [focusableOptions, inputValue, menuIsOpen, messages, options, screenReaderStatus, isLoading]);
|
|
112
128
|
var isInitialFocus = (ariaSelection === null || ariaSelection === void 0 ? void 0 : ariaSelection.action) === 'initial-input-focus';
|
|
113
129
|
var ariaGuidance = useMemo(function () {
|
|
130
|
+
if (fg('design_system_select-a11y-improvement')) {
|
|
131
|
+
// don't announce guidance at all when ff is on
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
114
134
|
var guidanceMsg = '';
|
|
115
135
|
if (messages.guidance) {
|
|
116
136
|
var context = focusedValue ? 'value' : menuIsOpen ? 'menu' : 'input';
|
|
@@ -132,16 +152,14 @@ var LiveRegion = function LiveRegion(props) {
|
|
|
132
152
|
id: "aria-focused"
|
|
133
153
|
}, ariaFocused), jsx("span", {
|
|
134
154
|
id: "aria-results"
|
|
135
|
-
}, ariaResults), jsx("span", {
|
|
155
|
+
}, ariaResults), !fg('design_system_select-a11y-improvement') && jsx("span", {
|
|
136
156
|
id: "aria-guidance"
|
|
137
157
|
}, ariaGuidance));
|
|
138
158
|
return jsx(Fragment, null, jsx(A11yText, {
|
|
139
159
|
id: id
|
|
140
160
|
}, isInitialFocus && ScreenReaderText), jsx(A11yText, {
|
|
141
|
-
"aria-live": ariaLive,
|
|
142
|
-
|
|
143
|
-
"aria-relevant": "additions text",
|
|
144
|
-
role: "log"
|
|
161
|
+
"aria-live": isA11yImprovementEnabled ? 'polite' : ariaLive,
|
|
162
|
+
role: isA11yImprovementEnabled ? 'status' : 'log'
|
|
145
163
|
}, isFocused && !isInitialFocus && ScreenReaderText));
|
|
146
164
|
};
|
|
147
165
|
|
|
@@ -307,7 +307,9 @@ export var MenuList = function MenuList(props) {
|
|
|
307
307
|
'menu-list--is-multi': isMulti
|
|
308
308
|
}), {
|
|
309
309
|
ref: innerRef
|
|
310
|
-
}, innerProps
|
|
310
|
+
}, innerProps, {
|
|
311
|
+
tabIndex: -1
|
|
312
|
+
}), children);
|
|
311
313
|
};
|
|
312
314
|
|
|
313
315
|
// ==============================
|
package/dist/esm/select.js
CHANGED
|
@@ -12,7 +12,8 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
12
12
|
function _createSuper(t) { var r = _isNativeReflectConstruct(); return function () { var e, o = _getPrototypeOf(t); if (r) { var s = _getPrototypeOf(this).constructor; e = Reflect.construct(o, arguments, s); } else e = o.apply(this, arguments); return _possibleConstructorReturn(this, e); }; }
|
|
13
13
|
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
|
|
14
14
|
import React, { Component } from 'react';
|
|
15
|
-
import {
|
|
15
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
16
|
+
import { isAppleDevice, isSafari } from './accessibility/helpers';
|
|
16
17
|
import { formatGroupLabel as formatGroupLabelBuiltin, getOptionLabel as getOptionLabelBuiltin, getOptionValue as getOptionValueBuiltin, isOptionDisabled as isOptionDisabledBuiltin } from './builtins';
|
|
17
18
|
import { defaultComponents } from './components';
|
|
18
19
|
import { DummyInput, RequiredInput, ScrollManager } from './components/internal';
|
|
@@ -248,7 +249,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
248
249
|
_defineProperty(_assertThisInitialized(_this), "initialTouchY", 0);
|
|
249
250
|
_defineProperty(_assertThisInitialized(_this), "openAfterFocus", false);
|
|
250
251
|
_defineProperty(_assertThisInitialized(_this), "scrollToFocusedOptionOnUpdate", false);
|
|
251
|
-
_defineProperty(_assertThisInitialized(_this), "isAppleDevice", isAppleDevice());
|
|
252
|
+
_defineProperty(_assertThisInitialized(_this), "isAppleDevice", fg('design_system_select-a11y-improvement') ? isSafari() : isAppleDevice());
|
|
252
253
|
// Refs
|
|
253
254
|
// ------------------------------
|
|
254
255
|
_defineProperty(_assertThisInitialized(_this), "controlRef", null);
|
|
@@ -1227,7 +1228,8 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1227
1228
|
labelId = _this$props8.labelId,
|
|
1228
1229
|
menuIsOpen = _this$props8.menuIsOpen,
|
|
1229
1230
|
required = _this$props8.required,
|
|
1230
|
-
tabIndex = _this$props8.tabIndex
|
|
1231
|
+
_this$props8$tabIndex = _this$props8.tabIndex,
|
|
1232
|
+
tabIndex = _this$props8$tabIndex === void 0 ? 0 : _this$props8$tabIndex;
|
|
1231
1233
|
var _this$getComponents = this.getComponents(),
|
|
1232
1234
|
Input = _this$getComponents.Input;
|
|
1233
1235
|
var _this$state4 = this.state,
|
|
@@ -1239,7 +1241,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1239
1241
|
|
|
1240
1242
|
// aria attributes makes the JSX "noisy", separated for clarity
|
|
1241
1243
|
var ariaAttributes = _objectSpread(_objectSpread(_objectSpread({
|
|
1242
|
-
'aria-autocomplete': '
|
|
1244
|
+
'aria-autocomplete': 'both',
|
|
1243
1245
|
'aria-errormessage': this.props['aria-errormessage'],
|
|
1244
1246
|
'aria-expanded': menuIsOpen,
|
|
1245
1247
|
'aria-haspopup': 'listbox',
|
|
@@ -1249,7 +1251,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1249
1251
|
'aria-labelledby': this.props['aria-labelledby'] || labelId,
|
|
1250
1252
|
'aria-required': required || isRequired,
|
|
1251
1253
|
role: 'combobox',
|
|
1252
|
-
'aria-activedescendant': this.
|
|
1254
|
+
'aria-activedescendant': this.state.focusedOptionId || undefined
|
|
1253
1255
|
}, menuIsOpen && {
|
|
1254
1256
|
'aria-controls': this.getElementId('listbox')
|
|
1255
1257
|
}), !isSearchable && {
|
|
@@ -1369,6 +1371,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1369
1371
|
ClearIndicator = _this$getComponents3.ClearIndicator;
|
|
1370
1372
|
var commonProps = this.commonProps;
|
|
1371
1373
|
var _this$props10 = this.props,
|
|
1374
|
+
clearControlLabel = _this$props10.clearControlLabel,
|
|
1372
1375
|
isDisabled = _this$props10.isDisabled,
|
|
1373
1376
|
isLoading = _this$props10.isLoading;
|
|
1374
1377
|
var isFocused = this.state.isFocused;
|
|
@@ -1380,7 +1383,9 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1380
1383
|
onTouchEnd: this.onClearIndicatorTouchEnd,
|
|
1381
1384
|
'aria-hidden': 'true'
|
|
1382
1385
|
};
|
|
1383
|
-
return /*#__PURE__*/React.createElement(ClearIndicator, _extends({
|
|
1386
|
+
return /*#__PURE__*/React.createElement(ClearIndicator, _extends({
|
|
1387
|
+
clearControlLabel: clearControlLabel
|
|
1388
|
+
}, commonProps, {
|
|
1384
1389
|
innerProps: innerProps,
|
|
1385
1390
|
isFocused: isFocused
|
|
1386
1391
|
}));
|
|
@@ -1478,13 +1483,15 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1478
1483
|
menuShouldScrollIntoView = _this$props12.menuShouldScrollIntoView,
|
|
1479
1484
|
noOptionsMessage = _this$props12.noOptionsMessage,
|
|
1480
1485
|
onMenuScrollToTop = _this$props12.onMenuScrollToTop,
|
|
1481
|
-
onMenuScrollToBottom = _this$props12.onMenuScrollToBottom
|
|
1486
|
+
onMenuScrollToBottom = _this$props12.onMenuScrollToBottom,
|
|
1487
|
+
labelId = _this$props12.labelId,
|
|
1488
|
+
label = _this$props12.label;
|
|
1482
1489
|
if (!menuIsOpen) {
|
|
1483
1490
|
return null;
|
|
1484
1491
|
}
|
|
1485
1492
|
|
|
1486
1493
|
// TODO: Internal Option Type here
|
|
1487
|
-
var render = function render(props, id) {
|
|
1494
|
+
var render = function render(props, id, headingId) {
|
|
1488
1495
|
var type = props.type,
|
|
1489
1496
|
data = props.data,
|
|
1490
1497
|
isDisabled = props.isDisabled,
|
|
@@ -1505,7 +1512,8 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1505
1512
|
onMouseMove: onHover,
|
|
1506
1513
|
onMouseOver: onHover,
|
|
1507
1514
|
role: 'option',
|
|
1508
|
-
'aria-selected':
|
|
1515
|
+
'aria-selected': !commonProps.isMulti && fg('design_system_select-a11y-improvement') ? isSelected || undefined : isSelected,
|
|
1516
|
+
'aria-describedby': fg('design_system_select-a11y-improvement') ? headingId : undefined
|
|
1509
1517
|
};
|
|
1510
1518
|
return /*#__PURE__*/React.createElement(Option, _extends({}, commonProps, {
|
|
1511
1519
|
innerProps: innerProps,
|
|
@@ -1522,7 +1530,8 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1522
1530
|
};
|
|
1523
1531
|
var menuUI;
|
|
1524
1532
|
if (this.hasOptions()) {
|
|
1525
|
-
|
|
1533
|
+
var items = this.getCategorizedOptions();
|
|
1534
|
+
menuUI = items.map(function (item) {
|
|
1526
1535
|
if (item.type === 'group') {
|
|
1527
1536
|
var data = item.data,
|
|
1528
1537
|
options = item.options,
|
|
@@ -1540,7 +1549,7 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1540
1549
|
},
|
|
1541
1550
|
label: _this4.formatGroupLabel(item.data)
|
|
1542
1551
|
}), item.options.map(function (option) {
|
|
1543
|
-
return render(option, "".concat(groupIndex, "-").concat(option.index));
|
|
1552
|
+
return render(option, "".concat(groupIndex, "-").concat(option.index), headingId);
|
|
1544
1553
|
}));
|
|
1545
1554
|
} else if (item.type === 'option') {
|
|
1546
1555
|
return render(item, "".concat(item.index));
|
|
@@ -1595,11 +1604,15 @@ var Select = /*#__PURE__*/function (_Component) {
|
|
|
1595
1604
|
_this4.getMenuListRef(instance);
|
|
1596
1605
|
scrollTargetRef(instance);
|
|
1597
1606
|
},
|
|
1598
|
-
innerProps: {
|
|
1607
|
+
innerProps: _objectSpread({
|
|
1599
1608
|
role: 'listbox',
|
|
1600
|
-
'aria-multiselectable
|
|
1609
|
+
// don't add aria-multiselectable when ff is on and the value is false
|
|
1610
|
+
'aria-multiselectable': fg('design_system_select-a11y-improvement') ? commonProps.isMulti || undefined : commonProps.isMulti,
|
|
1601
1611
|
id: _this4.getElementId('listbox')
|
|
1602
|
-
},
|
|
1612
|
+
}, fg('design_system_select-a11y-improvement') && {
|
|
1613
|
+
'aria-label': label,
|
|
1614
|
+
'aria-labelledby': labelId
|
|
1615
|
+
}),
|
|
1603
1616
|
isLoading: isLoading,
|
|
1604
1617
|
maxHeight: maxHeight,
|
|
1605
1618
|
focusedOption: focusedOption
|
|
@@ -94,6 +94,10 @@ export interface AriaOnFocusProps<Option, Group extends GroupBase<Option>> {
|
|
|
94
94
|
* Boolean indicating whether user uses Apple device
|
|
95
95
|
*/
|
|
96
96
|
isAppleDevice: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Boolean value of selectProp isMulti
|
|
99
|
+
*/
|
|
100
|
+
isMulti: boolean;
|
|
97
101
|
}
|
|
98
102
|
export type AriaGuidance = (props: AriaGuidanceProps) => string;
|
|
99
103
|
export type AriaOnChange<Option, IsMulti extends boolean> = (props: AriaOnChangeProps<Option, IsMulti>) => string;
|
|
@@ -35,6 +35,10 @@ export interface ClearIndicatorProps<Option = unknown, IsMulti extends boolean =
|
|
|
35
35
|
* The children to be rendered inside the indicator.
|
|
36
36
|
*/
|
|
37
37
|
children?: ReactNode;
|
|
38
|
+
/**
|
|
39
|
+
* Sets the `aria-label` for the clear icon button
|
|
40
|
+
*/
|
|
41
|
+
clearControlLabel?: string;
|
|
38
42
|
/**
|
|
39
43
|
* Props that will be passed on to the children.
|
|
40
44
|
*/
|
package/dist/types/select.d.ts
CHANGED
|
@@ -94,6 +94,10 @@ export interface SelectProps<Option, IsMulti extends boolean, Group extends Grou
|
|
|
94
94
|
* Provide classNames based on state for each inner component
|
|
95
95
|
*/
|
|
96
96
|
classNames: ClassNamesConfig<Option, IsMulti, Group>;
|
|
97
|
+
/**
|
|
98
|
+
* Set the `aria-label` for the clear icon button.
|
|
99
|
+
*/
|
|
100
|
+
clearControlLabel?: string;
|
|
97
101
|
/**
|
|
98
102
|
* Close the select menu when the user selects an option
|
|
99
103
|
*/
|
|
@@ -94,6 +94,10 @@ export interface AriaOnFocusProps<Option, Group extends GroupBase<Option>> {
|
|
|
94
94
|
* Boolean indicating whether user uses Apple device
|
|
95
95
|
*/
|
|
96
96
|
isAppleDevice: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Boolean value of selectProp isMulti
|
|
99
|
+
*/
|
|
100
|
+
isMulti: boolean;
|
|
97
101
|
}
|
|
98
102
|
export type AriaGuidance = (props: AriaGuidanceProps) => string;
|
|
99
103
|
export type AriaOnChange<Option, IsMulti extends boolean> = (props: AriaOnChangeProps<Option, IsMulti>) => string;
|
|
@@ -35,6 +35,10 @@ export interface ClearIndicatorProps<Option = unknown, IsMulti extends boolean =
|
|
|
35
35
|
* The children to be rendered inside the indicator.
|
|
36
36
|
*/
|
|
37
37
|
children?: ReactNode;
|
|
38
|
+
/**
|
|
39
|
+
* Sets the `aria-label` for the clear icon button
|
|
40
|
+
*/
|
|
41
|
+
clearControlLabel?: string;
|
|
38
42
|
/**
|
|
39
43
|
* Props that will be passed on to the children.
|
|
40
44
|
*/
|
|
@@ -94,6 +94,10 @@ export interface SelectProps<Option, IsMulti extends boolean, Group extends Grou
|
|
|
94
94
|
* Provide classNames based on state for each inner component
|
|
95
95
|
*/
|
|
96
96
|
classNames: ClassNamesConfig<Option, IsMulti, Group>;
|
|
97
|
+
/**
|
|
98
|
+
* Set the `aria-label` for the clear icon button.
|
|
99
|
+
*/
|
|
100
|
+
clearControlLabel?: string;
|
|
97
101
|
/**
|
|
98
102
|
* Close the select menu when the user selects an option
|
|
99
103
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/react-select",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.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",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@atlaskit/ds-lib": "^3.1.0",
|
|
30
|
+
"@atlaskit/platform-feature-flags": "^0.3.0",
|
|
30
31
|
"@babel/runtime": "^7.0.0",
|
|
31
32
|
"@emotion/react": "^11.7.1",
|
|
32
33
|
"@floating-ui/dom": "^1.0.1",
|
|
@@ -49,6 +50,11 @@
|
|
|
49
50
|
"typescript": "~5.4.2",
|
|
50
51
|
"wait-for-expect": "^1.2.0"
|
|
51
52
|
},
|
|
53
|
+
"platform-feature-flags": {
|
|
54
|
+
"design_system_select-a11y-improvement": {
|
|
55
|
+
"type": "boolean"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
52
58
|
"techstack": {
|
|
53
59
|
"@atlassian/frontend": {
|
|
54
60
|
"import-structure": [
|