@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 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
- var status = "".concat(isSelected ? ' selected' : '').concat(disabled);
65
- return "".concat(label).concat(status, ", ").concat(getArrayIndex(options, focused), ".");
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 resultsMessage = screenReaderStatus({
108
- count: focusableOptions.length
109
- });
110
- resultsMsg = messages.onFilter({
111
- inputValue: inputValue,
112
- resultsMessage: resultsMessage
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
- "aria-atomic": "false",
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), children);
314
+ }, innerProps, {
315
+ tabIndex: -1
316
+ }), children);
315
317
  };
316
318
 
317
319
  // ==============================
@@ -54,7 +54,9 @@ var Option = function Option(props) {
54
54
  }), {
55
55
  ref: innerRef,
56
56
  "aria-disabled": isDisabled
57
- }, innerProps), children);
57
+ }, innerProps, {
58
+ tabIndex: -1
59
+ }), children);
58
60
  };
59
61
 
60
62
  // eslint-disable-next-line @repo/internal/react/require-jsdoc
@@ -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': 'list',
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.isAppleDevice ? undefined : this.state.focusedOptionId ? this.state.focusedOptionId : undefined
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)({}, commonProps, {
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': _this4.isAppleDevice ? undefined : isSelected // is not supported on Apple devices
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
- menuUI = this.getCategorizedOptions().map(function (item) {
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': commonProps.isMulti,
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 ? `${arr.indexOf(item) + 1} of ${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
- const status = `${isSelected ? ' selected' : ''}${disabled}`;
61
- return `${label}${status}, ${getArrayIndex(options, focused)}.`;
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 resultsMessage = screenReaderStatus({
106
- count: focusableOptions.length
107
- });
108
- resultsMsg = messages.onFilter({
109
- inputValue,
110
- resultsMessage
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
- "aria-atomic": "false",
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
 
@@ -314,7 +314,9 @@ export const MenuList = props => {
314
314
  'menu-list--is-multi': isMulti
315
315
  }), {
316
316
  ref: innerRef
317
- }, innerProps), children);
317
+ }, innerProps, {
318
+ tabIndex: -1
319
+ }), children);
318
320
  };
319
321
 
320
322
  // ==============================
@@ -49,7 +49,9 @@ const Option = props => {
49
49
  }), {
50
50
  ref: innerRef,
51
51
  "aria-disabled": isDisabled
52
- }, innerProps), children);
52
+ }, innerProps, {
53
+ tabIndex: -1
54
+ }), children);
53
55
  };
54
56
 
55
57
  // eslint-disable-next-line @repo/internal/react/require-jsdoc
@@ -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 { isAppleDevice } from './accessibility/helpers';
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': 'list',
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.isAppleDevice ? undefined : this.state.focusedOptionId ? this.state.focusedOptionId : undefined,
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({}, commonProps, {
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': this.isAppleDevice ? undefined : isSelected // is not supported on Apple devices
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
- menuUI = this.getCategorizedOptions().map(item => {
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': commonProps.isMulti,
1640
- id: this.getElementId('listbox')
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
- var status = "".concat(isSelected ? ' selected' : '').concat(disabled);
59
- return "".concat(label).concat(status, ", ").concat(getArrayIndex(options, focused), ".");
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 resultsMessage = screenReaderStatus({
103
- count: focusableOptions.length
104
- });
105
- resultsMsg = messages.onFilter({
106
- inputValue: inputValue,
107
- resultsMessage: resultsMessage
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
- "aria-atomic": "false",
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), children);
310
+ }, innerProps, {
311
+ tabIndex: -1
312
+ }), children);
311
313
  };
312
314
 
313
315
  // ==============================
@@ -49,7 +49,9 @@ var Option = function Option(props) {
49
49
  }), {
50
50
  ref: innerRef,
51
51
  "aria-disabled": isDisabled
52
- }, innerProps), children);
52
+ }, innerProps, {
53
+ tabIndex: -1
54
+ }), children);
53
55
  };
54
56
 
55
57
  // eslint-disable-next-line @repo/internal/react/require-jsdoc
@@ -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 { isAppleDevice } from './accessibility/helpers';
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': 'list',
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.isAppleDevice ? undefined : this.state.focusedOptionId ? this.state.focusedOptionId : undefined
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({}, commonProps, {
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': _this4.isAppleDevice ? undefined : isSelected // is not supported on Apple devices
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
- menuUI = this.getCategorizedOptions().map(function (item) {
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': commonProps.isMulti,
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
@@ -1,4 +1,5 @@
1
1
  export declare function isIPhone(): boolean;
2
+ export declare function isSafari(): boolean;
2
3
  export declare function isMac(): boolean;
3
4
  export declare function isIPad(): boolean;
4
5
  export declare function isIOS(): boolean;
@@ -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
  */
@@ -1,4 +1,5 @@
1
1
  export declare function isIPhone(): boolean;
2
+ export declare function isSafari(): boolean;
2
3
  export declare function isMac(): boolean;
3
4
  export declare function isIPad(): boolean;
4
5
  export declare function isIOS(): boolean;
@@ -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.6",
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": [