@atlaskit/react-select 1.4.1 → 1.4.2

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,13 @@
1
1
  # @atlaskit/react-select
2
2
 
3
+ ## 1.4.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#172260](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/172260)
8
+ [`9934fe89f1e6a`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/9934fe89f1e6a) -
9
+ Improving assisstive technology support by adding better semantics and reducing live region usage
10
+
3
11
  ## 1.4.1
4
12
 
5
13
  ### Patch Changes
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.defaultAriaLiveMessages = void 0;
7
7
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
8
+ /* eslint-disable @atlaskit/platform/ensure-feature-flag-prefix */
9
+
8
10
  var defaultAriaLiveMessages = exports.defaultAriaLiveMessages = {
9
11
  guidance: function guidance(props) {
10
12
  var isSearchable = props.isSearchable,
@@ -33,11 +35,12 @@ var defaultAriaLiveMessages = exports.defaultAriaLiveMessages = {
33
35
  case 'deselect-option':
34
36
  case 'pop-value':
35
37
  case 'remove-value':
36
- return "option ".concat(label, ", deselected.");
38
+ return label.length && (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') || !(0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? "option ".concat(label, ", deselected") : '';
39
+ // TODO: this should be handled on backspace|delete if no value, but doing it here first
37
40
  case 'clear':
38
41
  return 'All selected options have been cleared.';
39
42
  case 'initial-input-focus':
40
- return "option".concat(labels.length > 1 ? 's' : '', " ").concat(labels.join(','), ", selected.");
43
+ return label.length && (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') || !(0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? "option".concat(labels.length > 1 ? 's' : '', " ").concat(labels.join(','), ", selected.") : '';
41
44
  case 'select-option':
42
45
  return isDisabled ? "option ".concat(label, " is disabled. Select another option.") : "option ".concat(label, ", selected.");
43
46
  default:
@@ -51,7 +54,6 @@ var defaultAriaLiveMessages = exports.defaultAriaLiveMessages = {
51
54
  _props$label2 = props.label,
52
55
  label = _props$label2 === void 0 ? '' : _props$label2,
53
56
  selectValue = props.selectValue,
54
- isMulti = props.isMulti,
55
57
  isDisabled = props.isDisabled,
56
58
  isSelected = props.isSelected;
57
59
  var getArrayIndex = function getArrayIndex(arr, item) {
@@ -60,11 +62,11 @@ var defaultAriaLiveMessages = exports.defaultAriaLiveMessages = {
60
62
  if (context === 'value' && selectValue) {
61
63
  return "value ".concat(label, " focused, ").concat(getArrayIndex(selectValue, focused), ".");
62
64
  }
63
- if (context === 'menu') {
65
+
66
+ // No longer needed after fg('design_system_select-a11y-improvement') is cleaned up
67
+ if (context === 'menu' && !(0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement')) {
64
68
  var disabled = isDisabled ? ' disabled' : '';
65
- // don't announce not selected for single selection
66
- var notSelectedStatus = !isMulti && (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? '' : ' not selected';
67
- var status = "".concat(isSelected ? ' selected' : notSelectedStatus).concat(disabled);
69
+ var status = "".concat(isSelected ? ' selected' : ' not selected').concat(disabled);
68
70
  return "".concat(label).concat(status, ", ").concat(getArrayIndex(options, focused), ", completion selected");
69
71
  }
70
72
  return '';
@@ -23,6 +23,8 @@ var styles = (0, _react.css)({
23
23
  clip: 'rect(1px, 1px, 1px, 1px)',
24
24
  label: 'a11yText',
25
25
  overflow: 'hidden',
26
+ userSelect: 'none',
27
+ // while hidden text is sitting in the DOM, it should not be selectable
26
28
  whiteSpace: 'nowrap'
27
29
  });
28
30
  var A11yText = function A11yText(props) {
@@ -12,7 +12,7 @@ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
12
12
  var _accessibility = require("../accessibility");
13
13
  var _a11yText = _interopRequireDefault(require("./internal/a11y-text"));
14
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; }
15
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /**
15
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /* eslint-disable @atlaskit/platform/ensure-feature-flag-prefix */ /**
16
16
  * @jsxRuntime classic
17
17
  * @jsx jsx
18
18
  */
@@ -170,8 +170,11 @@ var LiveRegion = function LiveRegion(props) {
170
170
  return (0, _react2.jsx)(_react.Fragment, null, (0, _react2.jsx)(_a11yText.default, {
171
171
  id: id
172
172
  }, isInitialFocus && ScreenReaderText), (0, _react2.jsx)(_a11yText.default, {
173
- "aria-live": isA11yImprovementEnabled ? 'polite' : ariaLive,
174
- role: isA11yImprovementEnabled ? 'status' : 'log'
173
+ "aria-live": ariaLive // Should be undefined by default unless a specific use case requires it
174
+ ,
175
+ "aria-atomic": (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? undefined : 'false',
176
+ "aria-relevant": (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? undefined : 'additions text',
177
+ role: (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? 'status' : 'log'
175
178
  }, isFocused && !isInitialFocus && ScreenReaderText));
176
179
  };
177
180
 
@@ -7,7 +7,10 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.optionCSS = exports.default = void 0;
8
8
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
9
9
  var _react = require("@emotion/react");
10
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
11
+ var _helpers = require("../accessibility/helpers");
10
12
  var _utils = require("../utils");
13
+ var _a11yText = _interopRequireDefault(require("./internal/a11y-text"));
11
14
  /**
12
15
  * @jsxRuntime classic
13
16
  * @jsx jsx
@@ -65,17 +68,18 @@ var Option = function Option(props) {
65
68
  isSelected = props.isSelected,
66
69
  innerRef = props.innerRef,
67
70
  innerProps = props.innerProps;
71
+ // eslint-disable-next-line @atlaskit/platform/ensure-feature-flag-prefix
72
+ var isVoiceOver = (0, _helpers.isAppleDevice)() && (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement');
68
73
  return (0, _react.jsx)("div", (0, _extends2.default)({}, (0, _utils.getStyleProps)(props, 'option', {
69
74
  option: true,
70
75
  'option--is-disabled': isDisabled,
71
76
  'option--is-focused': isFocused,
72
77
  'option--is-selected': isSelected
73
78
  }), {
74
- ref: innerRef,
75
- "aria-disabled": isDisabled
79
+ ref: innerRef
76
80
  }, innerProps, {
77
81
  tabIndex: -1
78
- }), children);
82
+ }), children, isVoiceOver && (isSelected || isDisabled) && (0, _react.jsx)(_a11yText.default, null, "".concat(isSelected ? ',selected' : '').concat(isDisabled ? ',dimmed' : '')));
79
83
  };
80
84
 
81
85
  // eslint-disable-next-line @repo/internal/react/require-jsdoc
@@ -32,9 +32,10 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
32
32
  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; }
33
33
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
34
34
  function _createSuper(t) { var r = _isNativeReflectConstruct(); return function () { var e, o = (0, _getPrototypeOf2.default)(t); if (r) { var s = (0, _getPrototypeOf2.default)(this).constructor; e = Reflect.construct(o, arguments, s); } else e = o.apply(this, arguments); return (0, _possibleConstructorReturn2.default)(this, e); }; }
35
- function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
35
+ function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } /* eslint-disable @atlaskit/platform/ensure-feature-flag-prefix */
36
36
  var defaultProps = exports.defaultProps = {
37
- 'aria-live': 'polite',
37
+ // aria-live is by default with the live region so we don't need it
38
+ 'aria-live': (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? undefined : 'polite',
38
39
  backspaceRemovesValue: true,
39
40
  blurInputOnSelect: (0, _utils.isTouchCapable)(),
40
41
  captureMenuScroll: !(0, _utils.isTouchCapable)(),
@@ -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, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? (0, _helpers.isSafari)() : (0, _helpers.isAppleDevice)());
262
+ (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "isVoiceOver", (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') && (0, _helpers.isAppleDevice)());
262
263
  // Refs
263
264
  // ------------------------------
264
265
  (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "controlRef", null);
@@ -759,6 +760,7 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
759
760
  prevInputValue: inputValue
760
761
  });
761
762
  _this.onMenuClose();
763
+ (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') && event.stopPropagation(); // keep ESC on select from dismissing parent layers
762
764
  } else if (isClearable && escapeClearsValue) {
763
765
  _this.clearValue();
764
766
  }
@@ -1162,6 +1164,27 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
1162
1164
  value: function formatGroupLabel(data) {
1163
1165
  return this.props.formatGroupLabel(data);
1164
1166
  }
1167
+ }, {
1168
+ key: "calculateDescription",
1169
+ value: function calculateDescription(action) {
1170
+ var descriptionProp = this.props['aria-describedby'] || this.props['descriptionId'];
1171
+ var isMulti = this.props.isMulti;
1172
+ var selectValue = this.state.selectValue;
1173
+ var defaultDescription = selectValue.length ? this.getElementId('live-region') : this.getElementId('placeholder');
1174
+ if (selectValue.length && action !== 'initial-input-focus') {
1175
+ return;
1176
+ }
1177
+ if (isMulti && (0, _helpers.isAppleDevice)() && !(0, _helpers.isSafari)()) {
1178
+ // chrome only friends
1179
+ return {
1180
+ 'aria-describedby': descriptionProp ? [descriptionProp, defaultDescription, this.getElementId('multi-message')].join(' ') : [defaultDescription, this.getElementId('multi-message')].join(' ')
1181
+ };
1182
+ } else {
1183
+ return {
1184
+ 'aria-describedby': descriptionProp ? [descriptionProp, defaultDescription].join(' ') : defaultDescription
1185
+ };
1186
+ }
1187
+ }
1165
1188
  }, {
1166
1189
  key: "startListeningComposition",
1167
1190
  value:
@@ -1245,30 +1268,28 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
1245
1268
  ariaSelection = _this$state4.ariaSelection;
1246
1269
  var commonProps = this.commonProps;
1247
1270
  var id = inputId || this.getElementId('input');
1248
- var description = this.props['aria-describedby'] || descriptionId;
1249
1271
 
1250
1272
  // aria attributes makes the JSX "noisy", separated for clarity
1251
1273
  var ariaAttributes = _objectSpread(_objectSpread(_objectSpread({
1252
1274
  'aria-autocomplete': 'both',
1253
1275
  'aria-errormessage': this.props['aria-errormessage'],
1254
1276
  'aria-expanded': menuIsOpen,
1277
+ // TODO: aria-haspopup is implied as listbox with role="combobox" and was deprecated for aria 1.2, we still might need to keep it for back compat
1255
1278
  'aria-haspopup': 'listbox',
1256
- 'aria-describedby': description,
1279
+ 'aria-describedby': this.props['aria-describedby'] || descriptionId,
1257
1280
  'aria-invalid': this.props['aria-invalid'] || isInvalid,
1258
1281
  'aria-label': this.props['aria-label'] || label,
1259
1282
  'aria-labelledby': this.props['aria-labelledby'] || labelId,
1260
1283
  'aria-required': required || isRequired,
1261
1284
  role: 'combobox',
1262
- 'aria-activedescendant': this.state.focusedOptionId || undefined
1285
+ 'aria-activedescendant': this.state.focusedOptionId || undefined,
1286
+ // Safari needs aria-owns in order for aria-activedescendant to work properly
1287
+ 'aria-owns': (0, _helpers.isSafari)() && (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? this.getElementId('listbox') : undefined
1263
1288
  }, menuIsOpen && {
1264
1289
  'aria-controls': this.getElementId('listbox')
1265
1290
  }), !isSearchable && {
1266
1291
  'aria-readonly': true
1267
- }), this.hasValue() ? (ariaSelection === null || ariaSelection === void 0 ? void 0 : ariaSelection.action) === 'initial-input-focus' && {
1268
- 'aria-describedby': description ? [description, this.getElementId('live-region')].join(' ') : this.getElementId('live-region')
1269
- } : {
1270
- 'aria-describedby': description ? [description, this.getElementId('placeholder')].join(' ') : this.getElementId('placeholder')
1271
- });
1292
+ }), this.calculateDescription(ariaSelection === null || ariaSelection === void 0 ? void 0 : ariaSelection.action));
1272
1293
  if (!isSearchable) {
1273
1294
  // use a dummy input to maintain focus/blur functionality
1274
1295
  return /*#__PURE__*/_react.default.createElement(_internal.DummyInput, (0, _extends2.default)({
@@ -1530,7 +1551,10 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
1530
1551
  onMouseMove: onHover,
1531
1552
  onMouseOver: onHover,
1532
1553
  role: 'option',
1533
- 'aria-selected': !commonProps.isMulti && (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? isSelected || undefined : isSelected,
1554
+ // We don't want aria-selected on Apple devices or if it's false. It does nasty things.
1555
+ 'aria-selected': (!commonProps.isMulti || _this4.isVoiceOver || !isSelected) && (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? undefined : isSelected,
1556
+ // We don't want aria-disabled on Apple devices or if it's false. It's just noisy.
1557
+ 'aria-disabled': ((0, _helpers.isAppleDevice)() || !isDisabled) && (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? undefined : isDisabled,
1534
1558
  'aria-describedby': (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? headingId : undefined
1535
1559
  };
1536
1560
  return /*#__PURE__*/_react.default.createElement(Option, (0, _extends2.default)({}, commonProps, {
@@ -1624,12 +1648,11 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
1624
1648
  },
1625
1649
  innerProps: _objectSpread({
1626
1650
  role: 'listbox',
1627
- // don't add aria-multiselectable when ff is on and the value is false
1628
- 'aria-multiselectable': (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? commonProps.isMulti || undefined : commonProps.isMulti,
1651
+ 'aria-multiselectable': (_this4.isVoiceOver || !commonProps.isMulti) && (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') ? undefined : commonProps.isMulti,
1629
1652
  id: _this4.getElementId('listbox')
1630
1653
  }, (0, _platformFeatureFlags.fg)('design_system_select-a11y-improvement') && {
1631
1654
  'aria-label': label,
1632
- 'aria-labelledby': labelId
1655
+ 'aria-labelledby': "".concat(labelId || _this4.getElementId('input'), " ").concat(commonProps.isMulti && (0, _helpers.isSafari)() ? _this4.getElementId('multi-message') : '')
1633
1656
  }),
1634
1657
  isLoading: isLoading,
1635
1658
  maxHeight: maxHeight,
@@ -1721,9 +1744,17 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
1721
1744
  isFocused: isFocused,
1722
1745
  selectValue: selectValue,
1723
1746
  focusableOptions: focusableOptions,
1724
- isAppleDevice: this.isAppleDevice
1747
+ isAppleDevice: this.isVoiceOver
1725
1748
  }));
1726
1749
  }
1750
+ }, {
1751
+ key: "renderMultiselectMessage",
1752
+ value: function renderMultiselectMessage() {
1753
+ return /*#__PURE__*/_react.default.createElement("span", {
1754
+ id: this.getElementId('multi-message'),
1755
+ hidden: true
1756
+ }, ", multiple selections available,");
1757
+ }
1727
1758
  }, {
1728
1759
  key: "render",
1729
1760
  value: function render() {
@@ -1754,7 +1785,7 @@ var Select = exports.default = /*#__PURE__*/function (_Component) {
1754
1785
  },
1755
1786
  isDisabled: isDisabled,
1756
1787
  isFocused: isFocused
1757
- }), this.renderLiveRegion(), /*#__PURE__*/_react.default.createElement(Control, (0, _extends2.default)({}, commonProps, {
1788
+ }), this.renderLiveRegion(), commonProps.isMulti && this.isVoiceOver && this.renderMultiselectMessage(), /*#__PURE__*/_react.default.createElement(Control, (0, _extends2.default)({}, commonProps, {
1758
1789
  innerRef: this.getControlRef,
1759
1790
  innerProps: {
1760
1791
  onMouseDown: this.onControlMouseDown,
@@ -1,3 +1,5 @@
1
+ /* eslint-disable @atlaskit/platform/ensure-feature-flag-prefix */
2
+
1
3
  import { fg } from '@atlaskit/platform-feature-flags';
2
4
  export const defaultAriaLiveMessages = {
3
5
  guidance: props => {
@@ -30,11 +32,12 @@ export const defaultAriaLiveMessages = {
30
32
  case 'deselect-option':
31
33
  case 'pop-value':
32
34
  case 'remove-value':
33
- return `option ${label}, deselected.`;
35
+ return label.length && fg('design_system_select-a11y-improvement') || !fg('design_system_select-a11y-improvement') ? `option ${label}, deselected` : '';
36
+ // TODO: this should be handled on backspace|delete if no value, but doing it here first
34
37
  case 'clear':
35
38
  return 'All selected options have been cleared.';
36
39
  case 'initial-input-focus':
37
- return `option${labels.length > 1 ? 's' : ''} ${labels.join(',')}, selected.`;
40
+ return label.length && fg('design_system_select-a11y-improvement') || !fg('design_system_select-a11y-improvement') ? `option${labels.length > 1 ? 's' : ''} ${labels.join(',')}, selected.` : '';
38
41
  case 'select-option':
39
42
  return isDisabled ? `option ${label} is disabled. Select another option.` : `option ${label}, selected.`;
40
43
  default:
@@ -48,7 +51,6 @@ export const defaultAriaLiveMessages = {
48
51
  options,
49
52
  label = '',
50
53
  selectValue,
51
- isMulti,
52
54
  isDisabled,
53
55
  isSelected
54
56
  } = props;
@@ -56,11 +58,11 @@ export const defaultAriaLiveMessages = {
56
58
  if (context === 'value' && selectValue) {
57
59
  return `value ${label} focused, ${getArrayIndex(selectValue, focused)}.`;
58
60
  }
59
- if (context === 'menu') {
61
+
62
+ // No longer needed after fg('design_system_select-a11y-improvement') is cleaned up
63
+ if (context === 'menu' && !fg('design_system_select-a11y-improvement')) {
60
64
  const disabled = isDisabled ? ' disabled' : '';
61
- // don't announce not selected for single selection
62
- const notSelectedStatus = !isMulti && fg('design_system_select-a11y-improvement') ? '' : ' not selected';
63
- const status = `${isSelected ? ' selected' : notSelectedStatus}${disabled}`;
65
+ const status = `${isSelected ? ' selected' : ' not selected'}${disabled}`;
64
66
  return `${label}${status}, ${getArrayIndex(options, focused)}, completion selected`;
65
67
  }
66
68
  return '';
@@ -16,6 +16,8 @@ const styles = css({
16
16
  clip: 'rect(1px, 1px, 1px, 1px)',
17
17
  label: 'a11yText',
18
18
  overflow: 'hidden',
19
+ userSelect: 'none',
20
+ // while hidden text is sitting in the DOM, it should not be selectable
19
21
  whiteSpace: 'nowrap'
20
22
  });
21
23
  const A11yText = props => jsx("span", _extends({
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @atlaskit/platform/ensure-feature-flag-prefix */
1
2
  /**
2
3
  * @jsxRuntime classic
3
4
  * @jsx jsx
@@ -168,8 +169,11 @@ const LiveRegion = props => {
168
169
  return jsx(Fragment, null, jsx(A11yText, {
169
170
  id: id
170
171
  }, isInitialFocus && ScreenReaderText), jsx(A11yText, {
171
- "aria-live": isA11yImprovementEnabled ? 'polite' : ariaLive,
172
- role: isA11yImprovementEnabled ? 'status' : 'log'
172
+ "aria-live": ariaLive // Should be undefined by default unless a specific use case requires it
173
+ ,
174
+ "aria-atomic": fg('design_system_select-a11y-improvement') ? undefined : 'false',
175
+ "aria-relevant": fg('design_system_select-a11y-improvement') ? undefined : 'additions text',
176
+ role: fg('design_system_select-a11y-improvement') ? 'status' : 'log'
173
177
  }, isFocused && !isInitialFocus && ScreenReaderText));
174
178
  };
175
179
 
@@ -5,7 +5,10 @@ import _extends from "@babel/runtime/helpers/extends";
5
5
  */
6
6
 
7
7
  import { jsx } from '@emotion/react';
8
+ import { fg } from '@atlaskit/platform-feature-flags';
9
+ import { isAppleDevice } from '../accessibility/helpers';
8
10
  import { getStyleProps } from '../utils';
11
+ import A11yText from './internal/a11y-text';
9
12
  export const optionCSS = ({
10
13
  isDisabled,
11
14
  isFocused,
@@ -61,17 +64,18 @@ const Option = props => {
61
64
  innerRef,
62
65
  innerProps
63
66
  } = props;
67
+ // eslint-disable-next-line @atlaskit/platform/ensure-feature-flag-prefix
68
+ const isVoiceOver = isAppleDevice() && fg('design_system_select-a11y-improvement');
64
69
  return jsx("div", _extends({}, getStyleProps(props, 'option', {
65
70
  option: true,
66
71
  'option--is-disabled': isDisabled,
67
72
  'option--is-focused': isFocused,
68
73
  'option--is-selected': isSelected
69
74
  }), {
70
- ref: innerRef,
71
- "aria-disabled": isDisabled
75
+ ref: innerRef
72
76
  }, innerProps, {
73
77
  tabIndex: -1
74
- }), children);
78
+ }), children, isVoiceOver && (isSelected || isDisabled) && jsx(A11yText, null, `${isSelected ? ',selected' : ''}${isDisabled ? ',dimmed' : ''}`));
75
79
  };
76
80
 
77
81
  // eslint-disable-next-line @repo/internal/react/require-jsdoc
@@ -1,5 +1,6 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
+ /* eslint-disable @atlaskit/platform/ensure-feature-flag-prefix */
3
4
  import React, { Component } from 'react';
4
5
  import { fg } from '@atlaskit/platform-feature-flags';
5
6
  import { isAppleDevice, isSafari } from './accessibility/helpers';
@@ -13,7 +14,8 @@ import { defaultStyles } from './styles';
13
14
  import { defaultTheme } from './theme';
14
15
  import { classNames, cleanValue, isDocumentElement, isMobileDevice, isTouchCapable, multiValueAsValue, noop, notNullish, scrollIntoView, singleValueAsValue, valueTernary } from './utils';
15
16
  export const defaultProps = {
16
- 'aria-live': 'polite',
17
+ // aria-live is by default with the live region so we don't need it
18
+ 'aria-live': fg('design_system_select-a11y-improvement') ? undefined : 'polite',
17
19
  backspaceRemovesValue: true,
18
20
  blurInputOnSelect: isTouchCapable(),
19
21
  captureMenuScroll: !isTouchCapable(),
@@ -225,7 +227,7 @@ export default class Select extends Component {
225
227
  _defineProperty(this, "initialTouchY", 0);
226
228
  _defineProperty(this, "openAfterFocus", false);
227
229
  _defineProperty(this, "scrollToFocusedOptionOnUpdate", false);
228
- _defineProperty(this, "isAppleDevice", fg('design_system_select-a11y-improvement') ? isSafari() : isAppleDevice());
230
+ _defineProperty(this, "isVoiceOver", fg('design_system_select-a11y-improvement') && isAppleDevice());
229
231
  // Refs
230
232
  // ------------------------------
231
233
  _defineProperty(this, "controlRef", null);
@@ -732,6 +734,7 @@ export default class Select extends Component {
732
734
  prevInputValue: inputValue
733
735
  });
734
736
  this.onMenuClose();
737
+ fg('design_system_select-a11y-improvement') && event.stopPropagation(); // keep ESC on select from dismissing parent layers
735
738
  } else if (isClearable && escapeClearsValue) {
736
739
  this.clearValue();
737
740
  }
@@ -1177,6 +1180,29 @@ export default class Select extends Component {
1177
1180
  formatGroupLabel(data) {
1178
1181
  return this.props.formatGroupLabel(data);
1179
1182
  }
1183
+ calculateDescription(action) {
1184
+ const descriptionProp = this.props['aria-describedby'] || this.props['descriptionId'];
1185
+ const {
1186
+ isMulti
1187
+ } = this.props;
1188
+ const {
1189
+ selectValue
1190
+ } = this.state;
1191
+ const defaultDescription = selectValue.length ? this.getElementId('live-region') : this.getElementId('placeholder');
1192
+ if (selectValue.length && action !== 'initial-input-focus') {
1193
+ return;
1194
+ }
1195
+ if (isMulti && isAppleDevice() && !isSafari()) {
1196
+ // chrome only friends
1197
+ return {
1198
+ 'aria-describedby': descriptionProp ? [descriptionProp, defaultDescription, this.getElementId('multi-message')].join(' ') : [defaultDescription, this.getElementId('multi-message')].join(' ')
1199
+ };
1200
+ } else {
1201
+ return {
1202
+ 'aria-describedby': descriptionProp ? [descriptionProp, defaultDescription].join(' ') : defaultDescription
1203
+ };
1204
+ }
1205
+ }
1180
1206
  // ==============================
1181
1207
  // Composition Handlers
1182
1208
  // ==============================
@@ -1251,32 +1277,31 @@ export default class Select extends Component {
1251
1277
  commonProps
1252
1278
  } = this;
1253
1279
  const id = inputId || this.getElementId('input');
1254
- const description = this.props['aria-describedby'] || descriptionId;
1255
1280
 
1256
1281
  // aria attributes makes the JSX "noisy", separated for clarity
1257
1282
  const ariaAttributes = {
1258
1283
  'aria-autocomplete': 'both',
1259
1284
  'aria-errormessage': this.props['aria-errormessage'],
1260
1285
  'aria-expanded': menuIsOpen,
1286
+ // TODO: aria-haspopup is implied as listbox with role="combobox" and was deprecated for aria 1.2, we still might need to keep it for back compat
1261
1287
  'aria-haspopup': 'listbox',
1262
- 'aria-describedby': description,
1288
+ 'aria-describedby': this.props['aria-describedby'] || descriptionId,
1263
1289
  'aria-invalid': this.props['aria-invalid'] || isInvalid,
1264
1290
  'aria-label': this.props['aria-label'] || label,
1265
1291
  'aria-labelledby': this.props['aria-labelledby'] || labelId,
1266
1292
  'aria-required': required || isRequired,
1267
1293
  role: 'combobox',
1268
1294
  'aria-activedescendant': this.state.focusedOptionId || undefined,
1295
+ // Safari needs aria-owns in order for aria-activedescendant to work properly
1296
+ 'aria-owns': isSafari() && fg('design_system_select-a11y-improvement') ? this.getElementId('listbox') : undefined,
1269
1297
  ...(menuIsOpen && {
1270
1298
  'aria-controls': this.getElementId('listbox')
1271
1299
  }),
1300
+ // TODO: Might need to remove this
1272
1301
  ...(!isSearchable && {
1273
1302
  'aria-readonly': true
1274
1303
  }),
1275
- ...(this.hasValue() ? (ariaSelection === null || ariaSelection === void 0 ? void 0 : ariaSelection.action) === 'initial-input-focus' && {
1276
- 'aria-describedby': description ? [description, this.getElementId('live-region')].join(' ') : this.getElementId('live-region')
1277
- } : {
1278
- 'aria-describedby': description ? [description, this.getElementId('placeholder')].join(' ') : this.getElementId('placeholder')
1279
- })
1304
+ ...this.calculateDescription(ariaSelection === null || ariaSelection === void 0 ? void 0 : ariaSelection.action)
1280
1305
  };
1281
1306
  if (!isSearchable) {
1282
1307
  // use a dummy input to maintain focus/blur functionality
@@ -1555,7 +1580,10 @@ export default class Select extends Component {
1555
1580
  onMouseMove: onHover,
1556
1581
  onMouseOver: onHover,
1557
1582
  role: 'option',
1558
- 'aria-selected': !commonProps.isMulti && fg('design_system_select-a11y-improvement') ? isSelected || undefined : isSelected,
1583
+ // We don't want aria-selected on Apple devices or if it's false. It does nasty things.
1584
+ 'aria-selected': (!commonProps.isMulti || this.isVoiceOver || !isSelected) && fg('design_system_select-a11y-improvement') ? undefined : isSelected,
1585
+ // We don't want aria-disabled on Apple devices or if it's false. It's just noisy.
1586
+ 'aria-disabled': (isAppleDevice() || !isDisabled) && fg('design_system_select-a11y-improvement') ? undefined : isDisabled,
1559
1587
  'aria-describedby': fg('design_system_select-a11y-improvement') ? headingId : undefined
1560
1588
  };
1561
1589
  return /*#__PURE__*/React.createElement(Option, _extends({}, commonProps, {
@@ -1649,13 +1677,12 @@ export default class Select extends Component {
1649
1677
  },
1650
1678
  innerProps: {
1651
1679
  role: 'listbox',
1652
- // don't add aria-multiselectable when ff is on and the value is false
1653
- 'aria-multiselectable': fg('design_system_select-a11y-improvement') ? commonProps.isMulti || undefined : commonProps.isMulti,
1680
+ 'aria-multiselectable': (this.isVoiceOver || !commonProps.isMulti) && fg('design_system_select-a11y-improvement') ? undefined : commonProps.isMulti,
1654
1681
  id: this.getElementId('listbox'),
1655
1682
  // add aditional label on listbox when ff is on
1656
1683
  ...(fg('design_system_select-a11y-improvement') && {
1657
1684
  'aria-label': label,
1658
- 'aria-labelledby': labelId
1685
+ 'aria-labelledby': `${labelId || this.getElementId('input')} ${commonProps.isMulti && isSafari() ? this.getElementId('multi-message') : ''}`
1659
1686
  })
1660
1687
  },
1661
1688
  isLoading: isLoading,
@@ -1743,9 +1770,15 @@ export default class Select extends Component {
1743
1770
  isFocused: isFocused,
1744
1771
  selectValue: selectValue,
1745
1772
  focusableOptions: focusableOptions,
1746
- isAppleDevice: this.isAppleDevice
1773
+ isAppleDevice: this.isVoiceOver
1747
1774
  }));
1748
1775
  }
1776
+ renderMultiselectMessage() {
1777
+ return /*#__PURE__*/React.createElement("span", {
1778
+ id: this.getElementId('multi-message'),
1779
+ hidden: true
1780
+ }, ", multiple selections available,");
1781
+ }
1749
1782
  render() {
1750
1783
  const {
1751
1784
  Control,
@@ -1776,7 +1809,7 @@ export default class Select extends Component {
1776
1809
  },
1777
1810
  isDisabled: isDisabled,
1778
1811
  isFocused: isFocused
1779
- }), this.renderLiveRegion(), /*#__PURE__*/React.createElement(Control, _extends({}, commonProps, {
1812
+ }), this.renderLiveRegion(), commonProps.isMulti && this.isVoiceOver && this.renderMultiselectMessage(), /*#__PURE__*/React.createElement(Control, _extends({}, commonProps, {
1780
1813
  innerRef: this.getControlRef,
1781
1814
  innerProps: {
1782
1815
  onMouseDown: this.onControlMouseDown,
@@ -1,3 +1,5 @@
1
+ /* eslint-disable @atlaskit/platform/ensure-feature-flag-prefix */
2
+
1
3
  import { fg } from '@atlaskit/platform-feature-flags';
2
4
  export var defaultAriaLiveMessages = {
3
5
  guidance: function guidance(props) {
@@ -27,11 +29,12 @@ export var defaultAriaLiveMessages = {
27
29
  case 'deselect-option':
28
30
  case 'pop-value':
29
31
  case 'remove-value':
30
- return "option ".concat(label, ", deselected.");
32
+ return label.length && fg('design_system_select-a11y-improvement') || !fg('design_system_select-a11y-improvement') ? "option ".concat(label, ", deselected") : '';
33
+ // TODO: this should be handled on backspace|delete if no value, but doing it here first
31
34
  case 'clear':
32
35
  return 'All selected options have been cleared.';
33
36
  case 'initial-input-focus':
34
- return "option".concat(labels.length > 1 ? 's' : '', " ").concat(labels.join(','), ", selected.");
37
+ return label.length && fg('design_system_select-a11y-improvement') || !fg('design_system_select-a11y-improvement') ? "option".concat(labels.length > 1 ? 's' : '', " ").concat(labels.join(','), ", selected.") : '';
35
38
  case 'select-option':
36
39
  return isDisabled ? "option ".concat(label, " is disabled. Select another option.") : "option ".concat(label, ", selected.");
37
40
  default:
@@ -45,7 +48,6 @@ export var defaultAriaLiveMessages = {
45
48
  _props$label2 = props.label,
46
49
  label = _props$label2 === void 0 ? '' : _props$label2,
47
50
  selectValue = props.selectValue,
48
- isMulti = props.isMulti,
49
51
  isDisabled = props.isDisabled,
50
52
  isSelected = props.isSelected;
51
53
  var getArrayIndex = function getArrayIndex(arr, item) {
@@ -54,11 +56,11 @@ export var defaultAriaLiveMessages = {
54
56
  if (context === 'value' && selectValue) {
55
57
  return "value ".concat(label, " focused, ").concat(getArrayIndex(selectValue, focused), ".");
56
58
  }
57
- if (context === 'menu') {
59
+
60
+ // No longer needed after fg('design_system_select-a11y-improvement') is cleaned up
61
+ if (context === 'menu' && !fg('design_system_select-a11y-improvement')) {
58
62
  var disabled = isDisabled ? ' disabled' : '';
59
- // don't announce not selected for single selection
60
- var notSelectedStatus = !isMulti && fg('design_system_select-a11y-improvement') ? '' : ' not selected';
61
- var status = "".concat(isSelected ? ' selected' : notSelectedStatus).concat(disabled);
63
+ var status = "".concat(isSelected ? ' selected' : ' not selected').concat(disabled);
62
64
  return "".concat(label).concat(status, ", ").concat(getArrayIndex(options, focused), ", completion selected");
63
65
  }
64
66
  return '';
@@ -16,6 +16,8 @@ var styles = css({
16
16
  clip: 'rect(1px, 1px, 1px, 1px)',
17
17
  label: 'a11yText',
18
18
  overflow: 'hidden',
19
+ userSelect: 'none',
20
+ // while hidden text is sitting in the DOM, it should not be selectable
19
21
  whiteSpace: 'nowrap'
20
22
  });
21
23
  var A11yText = function A11yText(props) {
@@ -1,6 +1,7 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  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; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ /* eslint-disable @atlaskit/platform/ensure-feature-flag-prefix */
4
5
  /**
5
6
  * @jsxRuntime classic
6
7
  * @jsx jsx
@@ -165,8 +166,11 @@ var LiveRegion = function LiveRegion(props) {
165
166
  return jsx(Fragment, null, jsx(A11yText, {
166
167
  id: id
167
168
  }, isInitialFocus && ScreenReaderText), jsx(A11yText, {
168
- "aria-live": isA11yImprovementEnabled ? 'polite' : ariaLive,
169
- role: isA11yImprovementEnabled ? 'status' : 'log'
169
+ "aria-live": ariaLive // Should be undefined by default unless a specific use case requires it
170
+ ,
171
+ "aria-atomic": fg('design_system_select-a11y-improvement') ? undefined : 'false',
172
+ "aria-relevant": fg('design_system_select-a11y-improvement') ? undefined : 'additions text',
173
+ role: fg('design_system_select-a11y-improvement') ? 'status' : 'log'
170
174
  }, isFocused && !isInitialFocus && ScreenReaderText));
171
175
  };
172
176
 
@@ -5,7 +5,10 @@ import _extends from "@babel/runtime/helpers/extends";
5
5
  */
6
6
 
7
7
  import { jsx } from '@emotion/react';
8
+ import { fg } from '@atlaskit/platform-feature-flags';
9
+ import { isAppleDevice } from '../accessibility/helpers';
8
10
  import { getStyleProps } from '../utils';
11
+ import A11yText from './internal/a11y-text';
9
12
  export var optionCSS = function optionCSS(_ref) {
10
13
  var isDisabled = _ref.isDisabled,
11
14
  isFocused = _ref.isFocused,
@@ -58,17 +61,18 @@ var Option = function Option(props) {
58
61
  isSelected = props.isSelected,
59
62
  innerRef = props.innerRef,
60
63
  innerProps = props.innerProps;
64
+ // eslint-disable-next-line @atlaskit/platform/ensure-feature-flag-prefix
65
+ var isVoiceOver = isAppleDevice() && fg('design_system_select-a11y-improvement');
61
66
  return jsx("div", _extends({}, getStyleProps(props, 'option', {
62
67
  option: true,
63
68
  'option--is-disabled': isDisabled,
64
69
  'option--is-focused': isFocused,
65
70
  'option--is-selected': isSelected
66
71
  }), {
67
- ref: innerRef,
68
- "aria-disabled": isDisabled
72
+ ref: innerRef
69
73
  }, innerProps, {
70
74
  tabIndex: -1
71
- }), children);
75
+ }), children, isVoiceOver && (isSelected || isDisabled) && jsx(A11yText, null, "".concat(isSelected ? ',selected' : '').concat(isDisabled ? ',dimmed' : '')));
72
76
  };
73
77
 
74
78
  // eslint-disable-next-line @repo/internal/react/require-jsdoc
@@ -11,6 +11,7 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
11
11
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
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
+ /* eslint-disable @atlaskit/platform/ensure-feature-flag-prefix */
14
15
  import React, { Component } from 'react';
15
16
  import { fg } from '@atlaskit/platform-feature-flags';
16
17
  import { isAppleDevice, isSafari } from './accessibility/helpers';
@@ -24,7 +25,8 @@ import { defaultStyles } from './styles';
24
25
  import { defaultTheme } from './theme';
25
26
  import { classNames, cleanValue, isDocumentElement, isMobileDevice, isTouchCapable, multiValueAsValue, noop, notNullish, scrollIntoView, singleValueAsValue, valueTernary } from './utils';
26
27
  export var defaultProps = {
27
- 'aria-live': 'polite',
28
+ // aria-live is by default with the live region so we don't need it
29
+ 'aria-live': fg('design_system_select-a11y-improvement') ? undefined : 'polite',
28
30
  backspaceRemovesValue: true,
29
31
  blurInputOnSelect: isTouchCapable(),
30
32
  captureMenuScroll: !isTouchCapable(),
@@ -248,7 +250,7 @@ var Select = /*#__PURE__*/function (_Component) {
248
250
  _defineProperty(_assertThisInitialized(_this), "initialTouchY", 0);
249
251
  _defineProperty(_assertThisInitialized(_this), "openAfterFocus", false);
250
252
  _defineProperty(_assertThisInitialized(_this), "scrollToFocusedOptionOnUpdate", false);
251
- _defineProperty(_assertThisInitialized(_this), "isAppleDevice", fg('design_system_select-a11y-improvement') ? isSafari() : isAppleDevice());
253
+ _defineProperty(_assertThisInitialized(_this), "isVoiceOver", fg('design_system_select-a11y-improvement') && isAppleDevice());
252
254
  // Refs
253
255
  // ------------------------------
254
256
  _defineProperty(_assertThisInitialized(_this), "controlRef", null);
@@ -749,6 +751,7 @@ var Select = /*#__PURE__*/function (_Component) {
749
751
  prevInputValue: inputValue
750
752
  });
751
753
  _this.onMenuClose();
754
+ fg('design_system_select-a11y-improvement') && event.stopPropagation(); // keep ESC on select from dismissing parent layers
752
755
  } else if (isClearable && escapeClearsValue) {
753
756
  _this.clearValue();
754
757
  }
@@ -1152,6 +1155,27 @@ var Select = /*#__PURE__*/function (_Component) {
1152
1155
  value: function formatGroupLabel(data) {
1153
1156
  return this.props.formatGroupLabel(data);
1154
1157
  }
1158
+ }, {
1159
+ key: "calculateDescription",
1160
+ value: function calculateDescription(action) {
1161
+ var descriptionProp = this.props['aria-describedby'] || this.props['descriptionId'];
1162
+ var isMulti = this.props.isMulti;
1163
+ var selectValue = this.state.selectValue;
1164
+ var defaultDescription = selectValue.length ? this.getElementId('live-region') : this.getElementId('placeholder');
1165
+ if (selectValue.length && action !== 'initial-input-focus') {
1166
+ return;
1167
+ }
1168
+ if (isMulti && isAppleDevice() && !isSafari()) {
1169
+ // chrome only friends
1170
+ return {
1171
+ 'aria-describedby': descriptionProp ? [descriptionProp, defaultDescription, this.getElementId('multi-message')].join(' ') : [defaultDescription, this.getElementId('multi-message')].join(' ')
1172
+ };
1173
+ } else {
1174
+ return {
1175
+ 'aria-describedby': descriptionProp ? [descriptionProp, defaultDescription].join(' ') : defaultDescription
1176
+ };
1177
+ }
1178
+ }
1155
1179
  }, {
1156
1180
  key: "startListeningComposition",
1157
1181
  value:
@@ -1235,30 +1259,28 @@ var Select = /*#__PURE__*/function (_Component) {
1235
1259
  ariaSelection = _this$state4.ariaSelection;
1236
1260
  var commonProps = this.commonProps;
1237
1261
  var id = inputId || this.getElementId('input');
1238
- var description = this.props['aria-describedby'] || descriptionId;
1239
1262
 
1240
1263
  // aria attributes makes the JSX "noisy", separated for clarity
1241
1264
  var ariaAttributes = _objectSpread(_objectSpread(_objectSpread({
1242
1265
  'aria-autocomplete': 'both',
1243
1266
  'aria-errormessage': this.props['aria-errormessage'],
1244
1267
  'aria-expanded': menuIsOpen,
1268
+ // TODO: aria-haspopup is implied as listbox with role="combobox" and was deprecated for aria 1.2, we still might need to keep it for back compat
1245
1269
  'aria-haspopup': 'listbox',
1246
- 'aria-describedby': description,
1270
+ 'aria-describedby': this.props['aria-describedby'] || descriptionId,
1247
1271
  'aria-invalid': this.props['aria-invalid'] || isInvalid,
1248
1272
  'aria-label': this.props['aria-label'] || label,
1249
1273
  'aria-labelledby': this.props['aria-labelledby'] || labelId,
1250
1274
  'aria-required': required || isRequired,
1251
1275
  role: 'combobox',
1252
- 'aria-activedescendant': this.state.focusedOptionId || undefined
1276
+ 'aria-activedescendant': this.state.focusedOptionId || undefined,
1277
+ // Safari needs aria-owns in order for aria-activedescendant to work properly
1278
+ 'aria-owns': isSafari() && fg('design_system_select-a11y-improvement') ? this.getElementId('listbox') : undefined
1253
1279
  }, menuIsOpen && {
1254
1280
  'aria-controls': this.getElementId('listbox')
1255
1281
  }), !isSearchable && {
1256
1282
  'aria-readonly': true
1257
- }), this.hasValue() ? (ariaSelection === null || ariaSelection === void 0 ? void 0 : ariaSelection.action) === 'initial-input-focus' && {
1258
- 'aria-describedby': description ? [description, this.getElementId('live-region')].join(' ') : this.getElementId('live-region')
1259
- } : {
1260
- 'aria-describedby': description ? [description, this.getElementId('placeholder')].join(' ') : this.getElementId('placeholder')
1261
- });
1283
+ }), this.calculateDescription(ariaSelection === null || ariaSelection === void 0 ? void 0 : ariaSelection.action));
1262
1284
  if (!isSearchable) {
1263
1285
  // use a dummy input to maintain focus/blur functionality
1264
1286
  return /*#__PURE__*/React.createElement(DummyInput, _extends({
@@ -1520,7 +1542,10 @@ var Select = /*#__PURE__*/function (_Component) {
1520
1542
  onMouseMove: onHover,
1521
1543
  onMouseOver: onHover,
1522
1544
  role: 'option',
1523
- 'aria-selected': !commonProps.isMulti && fg('design_system_select-a11y-improvement') ? isSelected || undefined : isSelected,
1545
+ // We don't want aria-selected on Apple devices or if it's false. It does nasty things.
1546
+ 'aria-selected': (!commonProps.isMulti || _this4.isVoiceOver || !isSelected) && fg('design_system_select-a11y-improvement') ? undefined : isSelected,
1547
+ // We don't want aria-disabled on Apple devices or if it's false. It's just noisy.
1548
+ 'aria-disabled': (isAppleDevice() || !isDisabled) && fg('design_system_select-a11y-improvement') ? undefined : isDisabled,
1524
1549
  'aria-describedby': fg('design_system_select-a11y-improvement') ? headingId : undefined
1525
1550
  };
1526
1551
  return /*#__PURE__*/React.createElement(Option, _extends({}, commonProps, {
@@ -1614,12 +1639,11 @@ var Select = /*#__PURE__*/function (_Component) {
1614
1639
  },
1615
1640
  innerProps: _objectSpread({
1616
1641
  role: 'listbox',
1617
- // don't add aria-multiselectable when ff is on and the value is false
1618
- 'aria-multiselectable': fg('design_system_select-a11y-improvement') ? commonProps.isMulti || undefined : commonProps.isMulti,
1642
+ 'aria-multiselectable': (_this4.isVoiceOver || !commonProps.isMulti) && fg('design_system_select-a11y-improvement') ? undefined : commonProps.isMulti,
1619
1643
  id: _this4.getElementId('listbox')
1620
1644
  }, fg('design_system_select-a11y-improvement') && {
1621
1645
  'aria-label': label,
1622
- 'aria-labelledby': labelId
1646
+ 'aria-labelledby': "".concat(labelId || _this4.getElementId('input'), " ").concat(commonProps.isMulti && isSafari() ? _this4.getElementId('multi-message') : '')
1623
1647
  }),
1624
1648
  isLoading: isLoading,
1625
1649
  maxHeight: maxHeight,
@@ -1711,9 +1735,17 @@ var Select = /*#__PURE__*/function (_Component) {
1711
1735
  isFocused: isFocused,
1712
1736
  selectValue: selectValue,
1713
1737
  focusableOptions: focusableOptions,
1714
- isAppleDevice: this.isAppleDevice
1738
+ isAppleDevice: this.isVoiceOver
1715
1739
  }));
1716
1740
  }
1741
+ }, {
1742
+ key: "renderMultiselectMessage",
1743
+ value: function renderMultiselectMessage() {
1744
+ return /*#__PURE__*/React.createElement("span", {
1745
+ id: this.getElementId('multi-message'),
1746
+ hidden: true
1747
+ }, ", multiple selections available,");
1748
+ }
1717
1749
  }, {
1718
1750
  key: "render",
1719
1751
  value: function render() {
@@ -1744,7 +1776,7 @@ var Select = /*#__PURE__*/function (_Component) {
1744
1776
  },
1745
1777
  isDisabled: isDisabled,
1746
1778
  isFocused: isFocused
1747
- }), this.renderLiveRegion(), /*#__PURE__*/React.createElement(Control, _extends({}, commonProps, {
1779
+ }), this.renderLiveRegion(), commonProps.isMulti && this.isVoiceOver && this.renderMultiselectMessage(), /*#__PURE__*/React.createElement(Control, _extends({}, commonProps, {
1748
1780
  innerRef: this.getControlRef,
1749
1781
  innerProps: {
1750
1782
  onMouseDown: this.onControlMouseDown,
@@ -413,7 +413,7 @@ export interface SelectProps<Option, IsMulti extends boolean, Group extends Grou
413
413
  [key: string]: any;
414
414
  }
415
415
  export declare const defaultProps: {
416
- 'aria-live': string;
416
+ 'aria-live': string | undefined;
417
417
  backspaceRemovesValue: boolean;
418
418
  blurInputOnSelect: boolean;
419
419
  captureMenuScroll: boolean;
@@ -491,7 +491,7 @@ interface CategorizedGroup<Option, Group extends GroupBase<Option>> {
491
491
  type CategorizedGroupOrOption<Option, Group extends GroupBase<Option>> = CategorizedGroup<Option, Group> | CategorizedOption<Option>;
492
492
  export default class Select<Option = unknown, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>> extends Component<SelectProps<Option, IsMulti, Group>, State<Option, IsMulti, Group>> {
493
493
  static defaultProps: {
494
- 'aria-live': string;
494
+ 'aria-live': string | undefined;
495
495
  backspaceRemovesValue: boolean;
496
496
  blurInputOnSelect: boolean;
497
497
  captureMenuScroll: boolean;
@@ -541,7 +541,7 @@ export default class Select<Option = unknown, IsMulti extends boolean = false, G
541
541
  openAfterFocus: boolean;
542
542
  scrollToFocusedOptionOnUpdate: boolean;
543
543
  userIsDragging?: boolean;
544
- isAppleDevice: boolean;
544
+ isVoiceOver: boolean;
545
545
  controlRef: HTMLDivElement | null;
546
546
  getControlRef: RefCallback<HTMLDivElement>;
547
547
  focusedOptionRef: HTMLDivElement | null;
@@ -609,7 +609,7 @@ export default class Select<Option = unknown, IsMulti extends boolean = false, G
609
609
  getOptionValue: (data: Option) => string;
610
610
  getStyles: <Key extends keyof StylesProps<Option, IsMulti, Group>>(key: Key, props: StylesProps<Option, IsMulti, Group>[Key]) => import("./types").CSSObjectWithLabel;
611
611
  getClassNames: <Key extends keyof StylesProps<Option, IsMulti, Group>>(key: Key, props: StylesProps<Option, IsMulti, Group>[Key]) => string | undefined;
612
- getElementId: (element: 'group' | 'input' | 'listbox' | 'option' | 'placeholder' | 'live-region') => string;
612
+ getElementId: (element: 'group' | 'input' | 'listbox' | 'option' | 'placeholder' | 'live-region' | 'multi-message') => string;
613
613
  getComponents: () => {
614
614
  ClearIndicator: <Option_1, IsMulti_1 extends boolean, Group_1 extends GroupBase<Option_1>>(props: import(".").ClearIndicatorProps<Option_1, IsMulti_1, Group_1>) => import("@emotion/react").jsx.JSX.Element;
615
615
  Control: <Option_2, IsMulti_2 extends boolean, Group_2 extends GroupBase<Option_2>>(props: import(".").ControlProps<Option_2, IsMulti_2, Group_2>) => import("@emotion/react").jsx.JSX.Element;
@@ -650,6 +650,9 @@ export default class Select<Option = unknown, IsMulti extends boolean = false, G
650
650
  filterOption(option: FilterOptionOption<Option>, inputValue: string): boolean;
651
651
  formatOptionLabel(data: Option, context: FormatOptionLabelContext): ReactNode;
652
652
  formatGroupLabel(data: Group): React.ReactNode;
653
+ calculateDescription(action?: String): {
654
+ 'aria-describedby': string;
655
+ } | undefined;
653
656
  onMenuMouseDown: MouseEventHandler<HTMLDivElement>;
654
657
  onMenuMouseMove: MouseEventHandler<HTMLDivElement>;
655
658
  onControlMouseDown: (event: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>) => void;
@@ -684,6 +687,7 @@ export default class Select<Option = unknown, IsMulti extends boolean = false, G
684
687
  renderMenu(): JSX.Element | null;
685
688
  renderFormField(): JSX.Element | undefined;
686
689
  renderLiveRegion(): JSX.Element;
690
+ renderMultiselectMessage(): JSX.Element;
687
691
  render(): JSX.Element;
688
692
  }
689
693
  export type PublicBaseSelectProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>> = JSX.LibraryManagedAttributes<typeof Select, SelectProps<Option, IsMulti, Group>>;
@@ -413,7 +413,7 @@ export interface SelectProps<Option, IsMulti extends boolean, Group extends Grou
413
413
  [key: string]: any;
414
414
  }
415
415
  export declare const defaultProps: {
416
- 'aria-live': string;
416
+ 'aria-live': string | undefined;
417
417
  backspaceRemovesValue: boolean;
418
418
  blurInputOnSelect: boolean;
419
419
  captureMenuScroll: boolean;
@@ -491,7 +491,7 @@ interface CategorizedGroup<Option, Group extends GroupBase<Option>> {
491
491
  type CategorizedGroupOrOption<Option, Group extends GroupBase<Option>> = CategorizedGroup<Option, Group> | CategorizedOption<Option>;
492
492
  export default class Select<Option = unknown, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>> extends Component<SelectProps<Option, IsMulti, Group>, State<Option, IsMulti, Group>> {
493
493
  static defaultProps: {
494
- 'aria-live': string;
494
+ 'aria-live': string | undefined;
495
495
  backspaceRemovesValue: boolean;
496
496
  blurInputOnSelect: boolean;
497
497
  captureMenuScroll: boolean;
@@ -541,7 +541,7 @@ export default class Select<Option = unknown, IsMulti extends boolean = false, G
541
541
  openAfterFocus: boolean;
542
542
  scrollToFocusedOptionOnUpdate: boolean;
543
543
  userIsDragging?: boolean;
544
- isAppleDevice: boolean;
544
+ isVoiceOver: boolean;
545
545
  controlRef: HTMLDivElement | null;
546
546
  getControlRef: RefCallback<HTMLDivElement>;
547
547
  focusedOptionRef: HTMLDivElement | null;
@@ -609,7 +609,7 @@ export default class Select<Option = unknown, IsMulti extends boolean = false, G
609
609
  getOptionValue: (data: Option) => string;
610
610
  getStyles: <Key extends keyof StylesProps<Option, IsMulti, Group>>(key: Key, props: StylesProps<Option, IsMulti, Group>[Key]) => import("./types").CSSObjectWithLabel;
611
611
  getClassNames: <Key extends keyof StylesProps<Option, IsMulti, Group>>(key: Key, props: StylesProps<Option, IsMulti, Group>[Key]) => string | undefined;
612
- getElementId: (element: 'group' | 'input' | 'listbox' | 'option' | 'placeholder' | 'live-region') => string;
612
+ getElementId: (element: 'group' | 'input' | 'listbox' | 'option' | 'placeholder' | 'live-region' | 'multi-message') => string;
613
613
  getComponents: () => {
614
614
  ClearIndicator: <Option_1, IsMulti_1 extends boolean, Group_1 extends GroupBase<Option_1>>(props: import(".").ClearIndicatorProps<Option_1, IsMulti_1, Group_1>) => import("@emotion/react").jsx.JSX.Element;
615
615
  Control: <Option_2, IsMulti_2 extends boolean, Group_2 extends GroupBase<Option_2>>(props: import(".").ControlProps<Option_2, IsMulti_2, Group_2>) => import("@emotion/react").jsx.JSX.Element;
@@ -650,6 +650,9 @@ export default class Select<Option = unknown, IsMulti extends boolean = false, G
650
650
  filterOption(option: FilterOptionOption<Option>, inputValue: string): boolean;
651
651
  formatOptionLabel(data: Option, context: FormatOptionLabelContext): ReactNode;
652
652
  formatGroupLabel(data: Group): React.ReactNode;
653
+ calculateDescription(action?: String): {
654
+ 'aria-describedby': string;
655
+ } | undefined;
653
656
  onMenuMouseDown: MouseEventHandler<HTMLDivElement>;
654
657
  onMenuMouseMove: MouseEventHandler<HTMLDivElement>;
655
658
  onControlMouseDown: (event: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>) => void;
@@ -684,6 +687,7 @@ export default class Select<Option = unknown, IsMulti extends boolean = false, G
684
687
  renderMenu(): JSX.Element | null;
685
688
  renderFormField(): JSX.Element | undefined;
686
689
  renderLiveRegion(): JSX.Element;
690
+ renderMultiselectMessage(): JSX.Element;
687
691
  render(): JSX.Element;
688
692
  }
689
693
  export type PublicBaseSelectProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>> = JSX.LibraryManagedAttributes<typeof Select, SelectProps<Option, IsMulti, Group>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/react-select",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
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",
@@ -28,9 +28,9 @@
28
28
  "./async-creatable": "./src/async-creatable.tsx"
29
29
  },
30
30
  "dependencies": {
31
- "@atlaskit/ds-lib": "^3.2.0",
31
+ "@atlaskit/ds-lib": "^3.3.0",
32
32
  "@atlaskit/platform-feature-flags": "^0.3.0",
33
- "@atlaskit/tokens": "^2.3.0",
33
+ "@atlaskit/tokens": "^2.4.0",
34
34
  "@babel/runtime": "^7.0.0",
35
35
  "@emotion/react": "^11.7.1",
36
36
  "@floating-ui/dom": "^1.0.1",