@atlaskit/react-select 2.6.6 → 2.7.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.
@@ -1,5 +1,4 @@
1
- import React, { Fragment, useMemo, useRef } from 'react';
2
- import { fg } from '@atlaskit/platform-feature-flags';
1
+ import React, { Fragment, useMemo } from 'react';
3
2
  import { defaultAriaLiveMessages } from '../../accessibility';
4
3
  import A11yText from './internal/a11y-text';
5
4
 
@@ -10,35 +9,24 @@ import A11yText from './internal/a11y-text';
10
9
  const LiveRegion = props => {
11
10
  const {
12
11
  ariaSelection,
13
- focusedOption,
14
- focusedValue,
15
12
  focusableOptions,
16
13
  isFocused,
17
14
  selectValue,
18
15
  selectProps,
19
- id,
20
- isAppleDevice
16
+ id
21
17
  } = props;
22
18
  const {
23
19
  ariaLiveMessages,
24
20
  getOptionLabel,
25
21
  inputValue,
26
- isMulti,
27
22
  isOptionDisabled,
28
- isSearchable,
29
- label,
30
23
  menuIsOpen,
31
24
  options,
32
25
  screenReaderStatus,
33
- tabSelectsValue,
34
26
  isLoading
35
27
  } = selectProps;
36
- const ariaLabel = selectProps['aria-label'] || label;
37
28
  const ariaLive = selectProps['aria-live'];
38
29
 
39
- // for safari, we will use minimum support from aria-live region
40
- const isA11yImprovementEnabled = fg('design_system_select-a11y-improvement') && !isAppleDevice;
41
-
42
30
  // Update aria live message configuration when prop changes
43
31
  const messages = useMemo(() => ({
44
32
  ...defaultAriaLiveMessages,
@@ -48,7 +36,7 @@ const LiveRegion = props => {
48
36
  // Update aria live selected option when prop changes
49
37
  const ariaSelected = useMemo(() => {
50
38
  let message = '';
51
- if (isA11yImprovementEnabled && menuIsOpen) {
39
+ if (menuIsOpen) {
52
40
  // we don't need to have selected message when the menu is open
53
41
  return '';
54
42
  }
@@ -70,7 +58,7 @@ const LiveRegion = props => {
70
58
  // If there are multiple items from the action then return an array of labels
71
59
  const multiSelected = selectedOptions || removedValues || undefined;
72
60
  const labels = multiSelected ? multiSelected.map(getOptionLabel) : [];
73
- if (isA11yImprovementEnabled && !label && !labels.length) {
61
+ if (!label && !labels.length) {
74
62
  // return empty string if no labels provided
75
63
  return '';
76
64
  }
@@ -85,33 +73,7 @@ const LiveRegion = props => {
85
73
  message = messages.onChange(onChangeProps);
86
74
  }
87
75
  return message;
88
- }, [ariaSelection, messages, isOptionDisabled, selectValue, getOptionLabel, isA11yImprovementEnabled, menuIsOpen]);
89
- const prevInputValue = useRef('');
90
- const ariaFocused = useMemo(() => {
91
- let focusMsg = '';
92
- const focused = focusedOption || focusedValue;
93
- const isSelected = !!(focusedOption && selectValue && selectValue.includes(focusedOption));
94
- if (inputValue === prevInputValue.current && isA11yImprovementEnabled) {
95
- // only announce focus option when searching when ff is on and the input value changed
96
- // for safari, we will announce for all
97
- return '';
98
- }
99
- if (focused && messages.onFocus) {
100
- const onFocusProps = {
101
- focused,
102
- label: getOptionLabel(focused),
103
- isDisabled: isOptionDisabled(focused, selectValue),
104
- isSelected,
105
- options: focusableOptions,
106
- context: focused === focusedOption ? 'menu' : 'value',
107
- selectValue,
108
- isMulti
109
- };
110
- focusMsg = messages.onFocus(onFocusProps);
111
- }
112
- prevInputValue.current = inputValue;
113
- return focusMsg;
114
- }, [inputValue, focusedOption, focusedValue, getOptionLabel, isOptionDisabled, messages, focusableOptions, selectValue, isA11yImprovementEnabled, isMulti]);
76
+ }, [ariaSelection, messages, isOptionDisabled, selectValue, getOptionLabel, menuIsOpen]);
115
77
  const ariaResults = useMemo(() => {
116
78
  let resultsMsg = '';
117
79
  if (isLoading) {
@@ -132,43 +94,17 @@ const LiveRegion = props => {
132
94
  return resultsMsg;
133
95
  }, [focusableOptions, inputValue, menuIsOpen, messages, options, screenReaderStatus, isLoading]);
134
96
  const isInitialFocus = (ariaSelection === null || ariaSelection === void 0 ? void 0 : ariaSelection.action) === 'initial-input-focus';
135
- const ariaGuidance = useMemo(() => {
136
- if (fg('design_system_select-a11y-improvement')) {
137
- // don't announce guidance at all when ff is on
138
- return '';
139
- }
140
- let guidanceMsg = '';
141
- if (messages.guidance) {
142
- const context = focusedValue ? 'value' : menuIsOpen ? 'menu' : 'input';
143
- guidanceMsg = messages.guidance({
144
- 'aria-label': ariaLabel,
145
- context,
146
- isDisabled: focusedOption && isOptionDisabled(focusedOption, selectValue),
147
- isMulti,
148
- isSearchable,
149
- tabSelectsValue,
150
- isInitialFocus
151
- });
152
- }
153
- return guidanceMsg;
154
- }, [ariaLabel, focusedOption, focusedValue, isMulti, isOptionDisabled, isSearchable, menuIsOpen, messages, selectValue, tabSelectsValue, isInitialFocus]);
155
97
  const ScreenReaderText = /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement("span", {
156
98
  id: "aria-selection"
157
99
  }, ariaSelected), /*#__PURE__*/React.createElement("span", {
158
100
  id: "aria-results"
159
- }, ariaResults), !fg('design_system_select-a11y-improvement') && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
160
- id: "aria-focused"
161
- }, ariaFocused), /*#__PURE__*/React.createElement("span", {
162
- id: "aria-guidance"
163
- }, ariaGuidance)));
101
+ }, ariaResults));
164
102
  return /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(A11yText, {
165
103
  id: id
166
104
  }, isInitialFocus && ScreenReaderText), /*#__PURE__*/React.createElement(A11yText, {
167
105
  "aria-live": ariaLive // Should be undefined by default unless a specific use case requires it
168
106
  ,
169
- "aria-atomic": fg('design_system_select-a11y-improvement') ? undefined : 'false',
170
- "aria-relevant": fg('design_system_select-a11y-improvement') ? undefined : 'additions text',
171
- role: fg('design_system_select-a11y-improvement') ? 'status' : 'log'
107
+ role: "status"
172
108
  }, isFocused && !isInitialFocus && ScreenReaderText));
173
109
  };
174
110
 
@@ -1,14 +1,12 @@
1
- /* eslint-disable @atlaskit/platform/ensure-feature-flag-prefix */
2
1
  /**
3
2
  * @jsxRuntime classic
4
3
  * @jsx jsx
5
4
  * @jsxFrag React.Fragment
6
5
  */
7
- import React, { Fragment, useMemo, useRef } from 'react';
6
+ import { Fragment, useMemo } from 'react';
8
7
 
9
8
  // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled
10
9
  import { jsx } from '@emotion/react';
11
- import { fg } from '@atlaskit/platform-feature-flags';
12
10
  import { defaultAriaLiveMessages } from '../../accessibility';
13
11
  import A11yText from './internal/a11y-text';
14
12
 
@@ -19,35 +17,24 @@ import A11yText from './internal/a11y-text';
19
17
  const LiveRegion = props => {
20
18
  const {
21
19
  ariaSelection,
22
- focusedOption,
23
- focusedValue,
24
20
  focusableOptions,
25
21
  isFocused,
26
22
  selectValue,
27
23
  selectProps,
28
- id,
29
- isAppleDevice
24
+ id
30
25
  } = props;
31
26
  const {
32
27
  ariaLiveMessages,
33
28
  getOptionLabel,
34
29
  inputValue,
35
- isMulti,
36
30
  isOptionDisabled,
37
- isSearchable,
38
- label,
39
31
  menuIsOpen,
40
32
  options,
41
33
  screenReaderStatus,
42
- tabSelectsValue,
43
34
  isLoading
44
35
  } = selectProps;
45
- const ariaLabel = selectProps['aria-label'] || label;
46
36
  const ariaLive = selectProps['aria-live'];
47
37
 
48
- // for safari, we will use minimum support from aria-live region
49
- const isA11yImprovementEnabled = fg('design_system_select-a11y-improvement') && !isAppleDevice;
50
-
51
38
  // Update aria live message configuration when prop changes
52
39
  const messages = useMemo(() => ({
53
40
  ...defaultAriaLiveMessages,
@@ -57,7 +44,7 @@ const LiveRegion = props => {
57
44
  // Update aria live selected option when prop changes
58
45
  const ariaSelected = useMemo(() => {
59
46
  let message = '';
60
- if (isA11yImprovementEnabled && menuIsOpen) {
47
+ if (menuIsOpen) {
61
48
  // we don't need to have selected message when the menu is open
62
49
  return '';
63
50
  }
@@ -79,7 +66,7 @@ const LiveRegion = props => {
79
66
  // If there are multiple items from the action then return an array of labels
80
67
  const multiSelected = selectedOptions || removedValues || undefined;
81
68
  const labels = multiSelected ? multiSelected.map(getOptionLabel) : [];
82
- if (isA11yImprovementEnabled && !label && !labels.length) {
69
+ if (!label && !labels.length) {
83
70
  // return empty string if no labels provided
84
71
  return '';
85
72
  }
@@ -94,33 +81,7 @@ const LiveRegion = props => {
94
81
  message = messages.onChange(onChangeProps);
95
82
  }
96
83
  return message;
97
- }, [ariaSelection, messages, isOptionDisabled, selectValue, getOptionLabel, isA11yImprovementEnabled, menuIsOpen]);
98
- const prevInputValue = useRef('');
99
- const ariaFocused = useMemo(() => {
100
- let focusMsg = '';
101
- const focused = focusedOption || focusedValue;
102
- const isSelected = !!(focusedOption && selectValue && selectValue.includes(focusedOption));
103
- if (inputValue === prevInputValue.current && isA11yImprovementEnabled) {
104
- // only announce focus option when searching when ff is on and the input value changed
105
- // for safari, we will announce for all
106
- return '';
107
- }
108
- if (focused && messages.onFocus) {
109
- const onFocusProps = {
110
- focused,
111
- label: getOptionLabel(focused),
112
- isDisabled: isOptionDisabled(focused, selectValue),
113
- isSelected,
114
- options: focusableOptions,
115
- context: focused === focusedOption ? 'menu' : 'value',
116
- selectValue,
117
- isMulti
118
- };
119
- focusMsg = messages.onFocus(onFocusProps);
120
- }
121
- prevInputValue.current = inputValue;
122
- return focusMsg;
123
- }, [inputValue, focusedOption, focusedValue, getOptionLabel, isOptionDisabled, messages, focusableOptions, selectValue, isA11yImprovementEnabled, isMulti]);
84
+ }, [ariaSelection, messages, isOptionDisabled, selectValue, getOptionLabel, menuIsOpen]);
124
85
  const ariaResults = useMemo(() => {
125
86
  let resultsMsg = '';
126
87
  if (isLoading) {
@@ -141,43 +102,17 @@ const LiveRegion = props => {
141
102
  return resultsMsg;
142
103
  }, [focusableOptions, inputValue, menuIsOpen, messages, options, screenReaderStatus, isLoading]);
143
104
  const isInitialFocus = (ariaSelection === null || ariaSelection === void 0 ? void 0 : ariaSelection.action) === 'initial-input-focus';
144
- const ariaGuidance = useMemo(() => {
145
- if (fg('design_system_select-a11y-improvement')) {
146
- // don't announce guidance at all when ff is on
147
- return '';
148
- }
149
- let guidanceMsg = '';
150
- if (messages.guidance) {
151
- const context = focusedValue ? 'value' : menuIsOpen ? 'menu' : 'input';
152
- guidanceMsg = messages.guidance({
153
- 'aria-label': ariaLabel,
154
- context,
155
- isDisabled: focusedOption && isOptionDisabled(focusedOption, selectValue),
156
- isMulti,
157
- isSearchable,
158
- tabSelectsValue,
159
- isInitialFocus
160
- });
161
- }
162
- return guidanceMsg;
163
- }, [ariaLabel, focusedOption, focusedValue, isMulti, isOptionDisabled, isSearchable, menuIsOpen, messages, selectValue, tabSelectsValue, isInitialFocus]);
164
105
  const ScreenReaderText = jsx(Fragment, null, jsx("span", {
165
106
  id: "aria-selection"
166
107
  }, ariaSelected), jsx("span", {
167
108
  id: "aria-results"
168
- }, ariaResults), !fg('design_system_select-a11y-improvement') && jsx(React.Fragment, null, jsx("span", {
169
- id: "aria-focused"
170
- }, ariaFocused), jsx("span", {
171
- id: "aria-guidance"
172
- }, ariaGuidance)));
109
+ }, ariaResults));
173
110
  return jsx(Fragment, null, jsx(A11yText, {
174
111
  id: id
175
112
  }, isInitialFocus && ScreenReaderText), jsx(A11yText, {
176
113
  "aria-live": ariaLive // Should be undefined by default unless a specific use case requires it
177
114
  ,
178
- "aria-atomic": fg('design_system_select-a11y-improvement') ? undefined : 'false',
179
- "aria-relevant": fg('design_system_select-a11y-improvement') ? undefined : 'additions text',
180
- role: fg('design_system_select-a11y-improvement') ? 'status' : 'log'
115
+ role: "status"
181
116
  }, isFocused && !isInitialFocus && ScreenReaderText));
182
117
  };
183
118
 
@@ -1,8 +1,7 @@
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 */
4
3
  import React, { Component } from 'react';
5
- import { isAppleDevice } from '@atlaskit/ds-lib/device-check';
4
+ import { isAppleDevice, isSafari } from '@atlaskit/ds-lib/device-check';
6
5
  import { fg } from '@atlaskit/platform-feature-flags';
7
6
  import { formatGroupLabel as formatGroupLabelBuiltin, getOptionLabel as getOptionLabelBuiltin, getOptionValue as getOptionValueBuiltin, isOptionDisabled as isOptionDisabledBuiltin } from './builtins';
8
7
  import { defaultComponents } from './components';
@@ -14,9 +13,6 @@ import { createFilter } from './filters';
14
13
  import { defaultStyles } from './styles';
15
14
  import { classNames, cleanValue, filterUnsupportedSelectors, isDocumentElement, isMobileDevice, isTouchCapable, multiValueAsValue, noop, notNullish, scrollIntoView, singleValueAsValue, valueTernary } from './utils';
16
15
  export const defaultProps = {
17
- // aria-live is by default with the live region so we don't need it
18
- // eslint-disable-next-line @atlaskit/platform/no-module-level-eval
19
- 'aria-live': fg('design_system_select-a11y-improvement') ? undefined : 'polite',
20
16
  backspaceRemovesValue: true,
21
17
  blurInputOnSelect: isTouchCapable(),
22
18
  captureMenuScroll: !isTouchCapable(),
@@ -230,7 +226,6 @@ export default class Select extends Component {
230
226
  _defineProperty(this, "initialTouchY", 0);
231
227
  _defineProperty(this, "openAfterFocus", false);
232
228
  _defineProperty(this, "scrollToFocusedOptionOnUpdate", false);
233
- _defineProperty(this, "isVoiceOver", fg('design_system_select-a11y-improvement') && isAppleDevice());
234
229
  // Refs
235
230
  // ------------------------------
236
231
  _defineProperty(this, "controlRef", null);
@@ -992,15 +987,62 @@ export default class Select extends Component {
992
987
  openAtIndex = selectedIndex;
993
988
  }
994
989
  }
990
+ const focusedOption = focusableOptions[openAtIndex];
995
991
 
996
992
  // only scroll if the menu isn't already open
997
993
  this.scrollToFocusedOptionOnUpdate = !(isFocused && this.menuListRef);
998
994
  this.setState({
999
995
  inputIsHiddenAfterUpdate: false,
1000
996
  focusedValue: null,
1001
- focusedOption: focusableOptions[openAtIndex],
1002
- focusedOptionId: this.getFocusedOptionId(focusableOptions[openAtIndex])
997
+ focusedOption: focusedOption,
998
+ focusedOptionId: this.getFocusedOptionId(focusedOption)
1003
999
  }, () => this.onMenuOpen());
1000
+ isSafari() && focusedOption && this.updateInputLabel(this.calculateInputLabel(focusedOption, openAtIndex));
1001
+ }
1002
+ updateInputLabel(inputLabel) {
1003
+ if (inputLabel) {
1004
+ var _this$inputRef;
1005
+ (_this$inputRef = this.inputRef) === null || _this$inputRef === void 0 ? void 0 : _this$inputRef.setAttribute('aria-label', inputLabel);
1006
+ setTimeout(() => {
1007
+ const normalizedLabel = this.props['aria-label'] || this.props.label;
1008
+ if (normalizedLabel) {
1009
+ var _this$inputRef2;
1010
+ (_this$inputRef2 = this.inputRef) === null || _this$inputRef2 === void 0 ? void 0 : _this$inputRef2.setAttribute('aria-label', normalizedLabel);
1011
+ } else {
1012
+ var _this$inputRef3;
1013
+ (_this$inputRef3 = this.inputRef) === null || _this$inputRef3 === void 0 ? void 0 : _this$inputRef3.removeAttribute('aria-label');
1014
+ }
1015
+ }, 500);
1016
+ }
1017
+ }
1018
+ calculateInputLabel(focusedOption, optionIndex) {
1019
+ const {
1020
+ options
1021
+ } = this.props;
1022
+ const isOptionsGrouped = options === null || options === void 0 ? void 0 : options.every(obj => typeof obj === 'object' && obj !== null && 'options' in obj);
1023
+ let inputLabel = this.getOptionLabel(focusedOption);
1024
+ const isOptionFocused = option => {
1025
+ return this.getOptionLabel(option) === inputLabel;
1026
+ };
1027
+ const groupData = options === null || options === void 0 ? void 0 : options.find(option => {
1028
+ var _groupCandidate$optio, _groupCandidate$optio2;
1029
+ const groupCandidate = option;
1030
+ return (_groupCandidate$optio = (_groupCandidate$optio2 = groupCandidate.options) === null || _groupCandidate$optio2 === void 0 ? void 0 : _groupCandidate$optio2.some(isOptionFocused)) !== null && _groupCandidate$optio !== void 0 ? _groupCandidate$optio : false;
1031
+ });
1032
+ if (isOptionsGrouped) {
1033
+ var _groupData$options$fi;
1034
+ const groupOptionIndex = (_groupData$options$fi = groupData === null || groupData === void 0 ? void 0 : groupData.options.findIndex(isOptionFocused)) !== null && _groupData$options$fi !== void 0 ? _groupData$options$fi : 0;
1035
+ const totalLength = options === null || options === void 0 ? void 0 : options.reduce((acc, currentGroup) => {
1036
+ var _group$options;
1037
+ const group = currentGroup;
1038
+ acc += group === null || group === void 0 ? void 0 : (_group$options = group.options) === null || _group$options === void 0 ? void 0 : _group$options.length;
1039
+ return acc;
1040
+ }, 0);
1041
+ inputLabel = `${inputLabel}, ${groupData === null || groupData === void 0 ? void 0 : groupData.label} (${groupOptionIndex + 1} of ${totalLength})`;
1042
+ } else {
1043
+ inputLabel = `${inputLabel} (${optionIndex + 1} of ${options === null || options === void 0 ? void 0 : options.length})`;
1044
+ }
1045
+ return inputLabel;
1004
1046
  }
1005
1047
  focusValue(direction) {
1006
1048
  const {
@@ -1174,28 +1216,38 @@ export default class Select extends Component {
1174
1216
  formatGroupLabel(data) {
1175
1217
  return this.props.formatGroupLabel(data);
1176
1218
  }
1177
- calculateDescription(action) {
1178
- const descriptionProp = this.props['aria-describedby'] || this.props['descriptionId'];
1219
+ calculateDescription() {
1220
+ const descriptionProp = this.props['aria-describedby'] || this.props.descriptionId;
1179
1221
  const {
1180
1222
  isMulti
1181
1223
  } = this.props;
1182
- const {
1183
- selectValue
1184
- } = this.state;
1185
- const defaultDescription = selectValue.length ? this.getElementId('live-region') : this.getElementId('placeholder');
1186
- if (!isMulti && selectValue.length && action !== 'initial-input-focus') {
1187
- return;
1224
+ const hasValue = this.state.selectValue.length > 0;
1225
+
1226
+ // Determine base description based on selection state
1227
+ const baseDescriptionId = hasValue ? isMulti ? '' : this.getElementId('single-value') : this.getElementId('placeholder');
1228
+
1229
+ // Fast path for single select with no description prop
1230
+ if (!isMulti && !descriptionProp) {
1231
+ return {
1232
+ 'aria-describedby': baseDescriptionId
1233
+ };
1234
+ }
1235
+
1236
+ // Build the describedby string efficiently
1237
+ let describedBy = baseDescriptionId;
1238
+ // Add description prop if it exists
1239
+ if (descriptionProp) {
1240
+ describedBy = describedBy ? `${descriptionProp} ${describedBy}` : descriptionProp;
1188
1241
  }
1242
+
1243
+ // For multi-select, always add multi-message ID
1189
1244
  if (isMulti) {
1190
1245
  const multiMessage = this.getElementId('multi-message');
1191
- return {
1192
- 'aria-describedby': descriptionProp ? [descriptionProp, defaultDescription, multiMessage].join(' ') : [defaultDescription, multiMessage].join(' ')
1193
- };
1194
- } else {
1195
- return {
1196
- 'aria-describedby': descriptionProp ? [descriptionProp, defaultDescription].join(' ') : defaultDescription
1197
- };
1246
+ describedBy = describedBy ? `${describedBy} ${multiMessage}` : multiMessage;
1198
1247
  }
1248
+ return {
1249
+ 'aria-describedby': describedBy
1250
+ };
1199
1251
  }
1200
1252
  // ==============================
1201
1253
  // Composition Handlers
@@ -1246,7 +1298,6 @@ export default class Select extends Component {
1246
1298
  // ==============================
1247
1299
  renderInput() {
1248
1300
  const {
1249
- descriptionId,
1250
1301
  form,
1251
1302
  inputId,
1252
1303
  inputValue,
@@ -1265,8 +1316,7 @@ export default class Select extends Component {
1265
1316
  Input
1266
1317
  } = this.getComponents();
1267
1318
  const {
1268
- inputIsHidden,
1269
- ariaSelection
1319
+ inputIsHidden
1270
1320
  } = this.state;
1271
1321
  const {
1272
1322
  commonProps
@@ -1280,7 +1330,6 @@ export default class Select extends Component {
1280
1330
  'aria-expanded': menuIsOpen,
1281
1331
  // 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
1282
1332
  'aria-haspopup': this.props['UNSAFE_is_experimental_generic'] ? 'dialog' : 'listbox',
1283
- 'aria-describedby': this.props['aria-describedby'] || descriptionId,
1284
1333
  'aria-invalid': this.props['aria-invalid'] || isInvalid,
1285
1334
  'aria-label': this.props['aria-label'] || label,
1286
1335
  'aria-labelledby': this.props['aria-labelledby'] || labelId,
@@ -1294,7 +1343,7 @@ export default class Select extends Component {
1294
1343
  ...(!isSearchable && {
1295
1344
  'aria-readonly': true
1296
1345
  }),
1297
- ...this.calculateDescription(ariaSelection === null || ariaSelection === void 0 ? void 0 : ariaSelection.action)
1346
+ ...this.calculateDescription()
1298
1347
  };
1299
1348
  if (!isSearchable) {
1300
1349
  // use a dummy input to maintain focus/blur functionality
@@ -1410,7 +1459,10 @@ export default class Select extends Component {
1410
1459
  const singleValue = selectValue[0];
1411
1460
  return /*#__PURE__*/React.createElement(SingleValue, _extends({}, commonProps, {
1412
1461
  data: singleValue,
1413
- isDisabled: isDisabled
1462
+ isDisabled: isDisabled,
1463
+ innerProps: {
1464
+ id: this.getElementId('single-value')
1465
+ }
1414
1466
  }), this.formatOptionLabel(singleValue, 'value'));
1415
1467
  }
1416
1468
  renderClearIndicator() {
@@ -1548,8 +1600,6 @@ export default class Select extends Component {
1548
1600
  noOptionsMessage,
1549
1601
  onMenuScrollToTop,
1550
1602
  onMenuScrollToBottom,
1551
- labelId,
1552
- label,
1553
1603
  testId
1554
1604
  } = this.props;
1555
1605
  if (!menuIsOpen) {
@@ -1578,8 +1628,8 @@ export default class Select extends Component {
1578
1628
  role: this.props['UNSAFE_is_experimental_generic'] ? 'listitem' : 'option',
1579
1629
  'aria-selected': this.props['UNSAFE_is_experimental_generic'] ? undefined : isSelected,
1580
1630
  // We don't want aria-disabled if it's false. It's just noisy.
1581
- 'aria-disabled': !isDisabled && fg('design_system_select-a11y-improvement') ? undefined : isDisabled,
1582
- 'aria-describedby': fg('design_system_select-a11y-improvement') ? headingId : undefined,
1631
+ 'aria-disabled': !isDisabled ? undefined : isDisabled,
1632
+ 'aria-describedby': headingId,
1583
1633
  ...(testId && {
1584
1634
  'data-testid': `${testId}-select--option-${id}`
1585
1635
  })
@@ -1657,26 +1707,6 @@ export default class Select extends Component {
1657
1707
  menuPosition,
1658
1708
  menuShouldScrollIntoView
1659
1709
  };
1660
- const calculateListboxLabel = () => {
1661
- var _this$inputRef;
1662
- // First in name calculation, overwrites aria-label
1663
- if (labelId) {
1664
- return {
1665
- 'aria-labelledby': labelId
1666
- };
1667
- }
1668
- // Second in name calcuation, overwrites everything else except aria-labelledby
1669
- if (label) {
1670
- return {
1671
- 'aria-label': label
1672
- };
1673
- }
1674
- // Fallback if no label or labelId is provided, might catch label via <label for> otherwise
1675
- // will most likely not have an accessible name
1676
- return {
1677
- 'aria-labelledby': ((_this$inputRef = this.inputRef) === null || _this$inputRef === void 0 ? void 0 : _this$inputRef.id) || this.getElementId('input')
1678
- };
1679
- };
1680
1710
  const menuElement = /*#__PURE__*/React.createElement(MenuPlacer, _extends({}, commonProps, menuPlacementProps), ({
1681
1711
  ref,
1682
1712
  placerProps: {
@@ -1700,28 +1730,35 @@ export default class Select extends Component {
1700
1730
  onTopArrive: onMenuScrollToTop,
1701
1731
  onBottomArrive: onMenuScrollToBottom,
1702
1732
  lockEnabled: menuShouldBlockScroll
1703
- }, scrollTargetRef => /*#__PURE__*/React.createElement(MenuList, _extends({}, commonProps, {
1704
- innerRef: instance => {
1705
- this.getMenuListRef(instance);
1706
- scrollTargetRef(instance);
1707
- },
1708
- innerProps: {
1709
- role: this.props['UNSAFE_is_experimental_generic'] ? 'dialog' : 'listbox',
1710
- 'aria-label': this.props['UNSAFE_is_experimental_generic'] ? `${this.props['aria-label'] || label}-dialog` : null,
1711
- 'aria-multiselectable': (this.isVoiceOver || !commonProps.isMulti) && fg('design_system_select-a11y-improvement') || this.props['UNSAFE_is_experimental_generic'] ? undefined : commonProps.isMulti,
1712
- id: this.getElementId('listbox'),
1713
- ...(testId && {
1714
- 'data-testid': `${testId}-select--listbox`
1715
- }),
1716
- // add aditional label on listbox when ff is on
1717
- ...(fg('design_system_select-a11y-improvement') && calculateListboxLabel())
1718
- },
1719
- isLoading: isLoading,
1720
- maxHeight: maxHeight,
1721
- focusedOption: focusedOption
1722
- }), this.props['UNSAFE_is_experimental_generic'] ? /*#__PURE__*/React.createElement("div", {
1723
- role: "list"
1724
- }, menuUI) : menuUI))));
1733
+ }, scrollTargetRef => {
1734
+ var _this$inputRef4, _this$inputRef5;
1735
+ return /*#__PURE__*/React.createElement(MenuList, _extends({}, commonProps, {
1736
+ innerRef: instance => {
1737
+ this.getMenuListRef(instance);
1738
+ scrollTargetRef(instance);
1739
+ },
1740
+ innerProps: {
1741
+ role: this.props['UNSAFE_is_experimental_generic'] ? 'dialog' : 'listbox',
1742
+ ...(this.props['UNSAFE_is_experimental_generic'] && {
1743
+ 'aria-labelledby': ((_this$inputRef4 = this.inputRef) === null || _this$inputRef4 === void 0 ? void 0 : _this$inputRef4.id) || this.getElementId('input')
1744
+ }),
1745
+ 'aria-multiselectable': !commonProps.isMulti || this.props['UNSAFE_is_experimental_generic'] ? undefined : commonProps.isMulti,
1746
+ id: this.getElementId('listbox'),
1747
+ ...(testId && {
1748
+ 'data-testid': `${testId}-select--listbox`
1749
+ }),
1750
+ // add aditional label on listbox for safari to announce first option
1751
+ ...(isSafari() && !this.props['UNSAFE_is_experimental_generic'] && {
1752
+ 'aria-describedby': ((_this$inputRef5 = this.inputRef) === null || _this$inputRef5 === void 0 ? void 0 : _this$inputRef5.id) || this.getElementId('input')
1753
+ })
1754
+ },
1755
+ isLoading: isLoading,
1756
+ maxHeight: maxHeight,
1757
+ focusedOption: focusedOption
1758
+ }), this.props['UNSAFE_is_experimental_generic'] ? /*#__PURE__*/React.createElement("div", {
1759
+ role: "list"
1760
+ }, menuUI) : menuUI);
1761
+ })));
1725
1762
 
1726
1763
  // positioning behaviour is almost identical for portalled and fixed,
1727
1764
  // so we use the same component. the actual portalling logic is forked
@@ -1789,8 +1826,6 @@ export default class Select extends Component {
1789
1826
  } = this;
1790
1827
  const {
1791
1828
  ariaSelection,
1792
- focusedOption,
1793
- focusedValue,
1794
1829
  isFocused,
1795
1830
  selectValue
1796
1831
  } = this.state;
@@ -1798,22 +1833,22 @@ export default class Select extends Component {
1798
1833
  return /*#__PURE__*/React.createElement(LiveRegion, _extends({}, commonProps, {
1799
1834
  id: this.getElementId('live-region'),
1800
1835
  ariaSelection: ariaSelection,
1801
- focusedOption: focusedOption,
1802
- focusedValue: focusedValue,
1803
1836
  isFocused: isFocused,
1804
1837
  selectValue: selectValue,
1805
- focusableOptions: focusableOptions,
1806
- isAppleDevice: this.isVoiceOver
1838
+ focusableOptions: focusableOptions
1807
1839
  }));
1808
1840
  }
1809
1841
  renderMultiselectMessage() {
1842
+ // In the future, when we actually support touch devices, we'll need to update this to not be keyboard specific.
1843
+ // Also, since this is rendered onscreen, it should be transtlated automatically.
1844
+ const msg = `, multiple selections available, ${this.state.selectValue.length ? 'Use left or right arrow keys to navigate selected items' : ''}`;
1810
1845
  return (
1811
1846
  /*#__PURE__*/
1812
1847
  // eslint-disable-next-line @atlaskit/design-system/use-primitives-text
1813
1848
  React.createElement("span", {
1814
1849
  id: this.getElementId('multi-message'),
1815
1850
  hidden: true
1816
- }, ", multiple selections available,")
1851
+ }, msg)
1817
1852
  );
1818
1853
  }
1819
1854
  render() {