diff --git a/packages/terra-alert/tests/wdio/alert-spec.js b/packages/terra-alert/tests/wdio/alert-spec.js
index fa580ef67be..c206f04c713 100644
--- a/packages/terra-alert/tests/wdio/alert-spec.js
+++ b/packages/terra-alert/tests/wdio/alert-spec.js
@@ -65,7 +65,7 @@ Terra.describeViewports('Alert', ['tiny', 'large'], () => {
it('alert content is focused when rendered with an action element', () => {
browser.url('/raw/tests/cerner-terra-core-docs/alert/custom-prop-alert');
- browser.keys(['Tab', 'Tab', 'Tab', 'Tab', 'Enter']);
+ browser.keys(['Tab', 'Tab', 'Tab', 'Tab', 'Tab', 'Tab', 'Enter']);
Terra.validates.element('alert focused');
});
diff --git a/packages/terra-core-docs/CHANGELOG.md b/packages/terra-core-docs/CHANGELOG.md
index a64a620c0ab..3bfba3d1691 100644
--- a/packages/terra-core-docs/CHANGELOG.md
+++ b/packages/terra-core-docs/CHANGELOG.md
@@ -2,6 +2,9 @@
## Unreleased
+* Added
+ * Added test example for `terra-form-select`.
+
## 1.73.0 - (April 25, 2024)
* Changed
diff --git a/packages/terra-core-docs/src/terra-dev-site/test/form-select/ControlMultipleDisabled.test.jsx b/packages/terra-core-docs/src/terra-dev-site/test/form-select/ControlMultipleDisabled.test.jsx
new file mode 100644
index 00000000000..b31ad29554e
--- /dev/null
+++ b/packages/terra-core-docs/src/terra-dev-site/test/form-select/ControlMultipleDisabled.test.jsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import classNames from 'classnames/bind';
+import Select from 'terra-form-select';
+import styles from './common/Select.test.module.scss';
+
+const cx = classNames.bind(styles);
+
+class ControlledMultipleDisabled extends React.Component {
+ constructor() {
+ super();
+
+ this.state = { value: ['blue', 'red'] };
+ this.handleChange = this.handleChange.bind(this);
+ }
+
+ handleChange(value) {
+ this.setState({ value });
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+
+export default ControlledMultipleDisabled;
diff --git a/packages/terra-form-select/CHANGELOG.md b/packages/terra-form-select/CHANGELOG.md
index aae2ea33d22..46db636699f 100644
--- a/packages/terra-form-select/CHANGELOG.md
+++ b/packages/terra-form-select/CHANGELOG.md
@@ -2,6 +2,12 @@
## Unreleased
+* Added
+ * Added visual focus dashed border for `terra-form-select` tags.
+
+* Fixed
+ * Fixed accessibility issue in `MultiSelect` component.
+
## 6.61.0 - (April 4, 2024)
* Fixed
diff --git a/packages/terra-form-select/src/MultiSelect.jsx b/packages/terra-form-select/src/MultiSelect.jsx
index ede1fe3aea2..cb7c0ca2a66 100644
--- a/packages/terra-form-select/src/MultiSelect.jsx
+++ b/packages/terra-form-select/src/MultiSelect.jsx
@@ -137,12 +137,23 @@ class MultiSelect extends React.Component {
this.state = {
value: SelectUtil.defaultValue({ defaultValue, value, multiple: true }),
+ isInputFocused: false,
};
-
+ this.inputRef = null;
this.display = this.display.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleDeselect = this.handleDeselect.bind(this);
this.handleSelect = this.handleSelect.bind(this);
+ this.handleFocus = this.handleFocus.bind(this);
+ this.handleBlur = this.handleBlur.bind(this);
+ this.handleInputRef = this.handleInputRef.bind(this);
+ }
+
+ componentWillUnmount() {
+ if (this.inputRef) {
+ this.inputRef.removeEventListener('focus', this.handleFocus);
+ this.inputRef.removeEventListener('blur', this.handleBlur);
+ }
}
/**
@@ -185,6 +196,25 @@ class MultiSelect extends React.Component {
}
}
+ handleFocus() { this.setState({ isInputFocused: true }); }
+
+ handleBlur() { this.setState({ isInputFocused: false }); }
+
+ /**
+ * Receives the reference to the input element from the Frame component.
+ * Attaches event listeners to handle focus and blur events, updating the state accordingly.
+ * @param {HTMLElement} ref - Reference to the input element.
+ */
+ handleInputRef(ref) {
+ // Receive the input reference from the Frame
+ this.inputRef = ref;
+
+ if (this.inputRef) {
+ this.inputRef.addEventListener('focus', this.handleFocus);
+ this.inputRef.addEventListener('blur', this.handleBlur);
+ }
+ }
+
/**
* Returns the appropriate variant display
*/
@@ -192,7 +222,14 @@ class MultiSelect extends React.Component {
const selectValue = SelectUtil.value(this.props, this.state);
return selectValue.map(tag => (
-
+
{SelectUtil.valueDisplay(this.props, tag)}
));
@@ -218,6 +255,7 @@ class MultiSelect extends React.Component {
required={required}
totalOptions={SelectUtil.getTotalNumberOfOptions(children)}
inputId={inputId}
+ getInputRef={this.handleInputRef}
>
{children}
diff --git a/packages/terra-form-select/src/clinical-lowlight-theme/Tag.module.scss b/packages/terra-form-select/src/clinical-lowlight-theme/Tag.module.scss
index 0579f6ca9cd..51a18f6edbf 100644
--- a/packages/terra-form-select/src/clinical-lowlight-theme/Tag.module.scss
+++ b/packages/terra-form-select/src/clinical-lowlight-theme/Tag.module.scss
@@ -20,6 +20,7 @@
--terra-form-select-tag-deselect-hover-border-bottom: 1px solid #181b1d;
--terra-form-select-tag-icon-height: 0.7142857142857143rem;
--terra-form-select-tag-icon-width: 0.7142857142857143rem;
+ --terra-form-select-tag-focus-outline: 2px dashed #b2b5b6;
@include terra-inline-svg-var('--terra-form-select-tag-icon-background' , '');
}
diff --git a/packages/terra-form-select/src/multiple/Frame.jsx b/packages/terra-form-select/src/multiple/Frame.jsx
index f1a0c7283f3..88ab29e8efe 100644
--- a/packages/terra-form-select/src/multiple/Frame.jsx
+++ b/packages/terra-form-select/src/multiple/Frame.jsx
@@ -121,6 +121,10 @@ const propTypes = {
* The select value.
*/
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array]),
+ /**
+ * Returns the input ref to the Parent component.
+ */
+ getInputRef: PropTypes.func,
};
const defaultProps = {
@@ -202,6 +206,8 @@ class Frame extends React.Component {
// eslint-disable-next-line global-require
require('wicg-inert/dist/inert');
}
+
+ this.props.getInputRef(this.input);
}
componentDidUpdate(previousProps, previousState) {
diff --git a/packages/terra-form-select/src/orion-fusion-theme/Tag.module.scss b/packages/terra-form-select/src/orion-fusion-theme/Tag.module.scss
index 03920134922..0dcefc319ae 100644
--- a/packages/terra-form-select/src/orion-fusion-theme/Tag.module.scss
+++ b/packages/terra-form-select/src/orion-fusion-theme/Tag.module.scss
@@ -20,6 +20,8 @@
--terra-form-select-tag-deselect-hover-border-bottom: 1px solid #dedfe0;
--terra-form-select-tag-icon-height: 0.91667rem;
--terra-form-select-tag-icon-width: 0.91667rem;
+ --terra-form-select-tag-focus-box-shadow: rgba(76, 178, 233, 0.5) 0 0 1px 3px inset;
+ --terra-form-select-tag-focus-outline: none;
@include terra-inline-svg-var('--terra-form-select-tag-icon-background', '');
}
diff --git a/packages/terra-form-select/src/shared/_Tag.jsx b/packages/terra-form-select/src/shared/_Tag.jsx
index 773ebf53cd0..fe96d2f635a 100644
--- a/packages/terra-form-select/src/shared/_Tag.jsx
+++ b/packages/terra-form-select/src/shared/_Tag.jsx
@@ -1,7 +1,8 @@
-import React from 'react';
+import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import classNamesBind from 'classnames/bind';
import ThemeContext from 'terra-theme-context';
+import { injectIntl } from 'react-intl';
import styles from './_Tag.module.scss';
const cx = classNamesBind.bind(styles);
@@ -19,17 +20,75 @@ const propTypes = {
* The value of the tag.
*/
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+ /**
+ * Specifies whether the tag is disabled.
+ */
+ disabled: PropTypes.bool,
+ /**
+ * @private
+ * The intl object containing translations. This is retrieved from the context automatically by injectIntl.
+ */
+ intl: PropTypes.shape({ formatMessage: PropTypes.func }).isRequired,
+ /**
+ * Ref object for accessing the underlying input element of the tag component.
+ */
+ inputRef: PropTypes.shape({
+ focus: PropTypes.instanceOf(Element),
+ }),
+ /**
+ * Specifies whether the input focus is set to true or false.
+ * Default is false.
+ */
+ isInputFocused: PropTypes.bool,
};
/* eslint-disable jsx-a11y/no-static-element-interactions */
-const Tag = ({ children, onDeselect, value }) => {
+const Tag = ({
+ children, onDeselect, value, disabled, intl, inputRef, isInputFocused,
+}) => {
const theme = React.useContext(ThemeContext);
+ const tagRef = useRef(null);
+
+ const handleKeyPress = (event) => {
+ if ((event.key === 'Enter' || event.key === 'Backspace') && !disabled) {
+ event.stopPropagation();
+ onDeselect(value);
+ const previousLi = tagRef.current.previousElementSibling;
+ if (previousLi) {
+ const deselectElement = previousLi.querySelector(':scope > :nth-child(2)');
+ if (deselectElement) {
+ deselectElement.focus();
+ }
+ } else {
+ const nextLi = tagRef.current.nextElementSibling;
+ if (nextLi) {
+ const nextFocusableElement = nextLi.querySelector(':scope > :nth-child(2)');
+ if (nextFocusableElement) {
+ nextFocusableElement.focus();
+ return;
+ }
+ }
+ inputRef.focus();
+ }
+ }
+ };
+
+ const attributes = isInputFocused ? { role: 'presentation' }
+ : { role: 'button', 'aria-label': intl.formatMessage({ id: 'Terra.form.select.deselect' }, { text: children }) };
return (
-
+
{children}
- { onDeselect(value); }} role="presentation">
+ { if (!disabled) onDeselect(value); }}
+ tabIndex={!disabled ? 0 : -1}
+ role="button"
+ {...attributes}
+ >
@@ -38,4 +97,4 @@ const Tag = ({ children, onDeselect, value }) => {
Tag.propTypes = propTypes;
-export default Tag;
+export default injectIntl(Tag);
diff --git a/packages/terra-form-select/src/shared/_Tag.module.scss b/packages/terra-form-select/src/shared/_Tag.module.scss
index 59fc66068fe..31176e0d9fa 100644
--- a/packages/terra-form-select/src/shared/_Tag.module.scss
+++ b/packages/terra-form-select/src/shared/_Tag.module.scss
@@ -43,6 +43,12 @@
background: var(--terra-form-select-tag-deselect-hover-background, #b9bbbc);
border-bottom: var(--terra-form-select-tag-deselect-hover-border-bottom, 0.14286rem solid #8f8f90);
}
+
+ &:focus {
+ outline: var(--terra-form-select-tag-focus-outline, 2px dashed #000);
+ outline-offset: -2px;
+ box-shadow: var(--terra-form-select-tag-focus-box-shadow, none);
+ }
}
.icon {
diff --git a/packages/terra-form-select/tests/jest/Tag.test.jsx b/packages/terra-form-select/tests/jest/Tag.test.jsx
index aa3530322d7..931e13986a1 100644
--- a/packages/terra-form-select/tests/jest/Tag.test.jsx
+++ b/packages/terra-form-select/tests/jest/Tag.test.jsx
@@ -5,12 +5,14 @@ import Tag from '../../src/shared/_Tag';
describe('Tag', () => {
it('should render a default Tag', () => {
- const wrapper = enzyme.shallow( {}}>Content);
+ const wrapper = enzymeIntl.shallowWithIntl(
+ {}}>Content,
+ );
expect(wrapper).toMatchSnapshot();
});
it('correctly applies the theme context className', () => {
- const wrapper = enzyme.mount(
+ const wrapper = enzymeIntl.mountWithIntl(
{}}>
Content
diff --git a/packages/terra-form-select/tests/jest/__snapshots__/Tag.test.jsx.snap b/packages/terra-form-select/tests/jest/__snapshots__/Tag.test.jsx.snap
index 9de44f073d3..84e2dc7f0fa 100644
--- a/packages/terra-form-select/tests/jest/__snapshots__/Tag.test.jsx.snap
+++ b/packages/terra-form-select/tests/jest/__snapshots__/Tag.test.jsx.snap
@@ -2,55 +2,133 @@
exports[`Tag correctly applies the theme context className 1`] = `
-
-
-
- Content
-
-
-
-
-
+ className="display"
+ >
+ Content
+
+
+
+
+
+
+
`;
exports[`Tag should render a default Tag 1`] = `
-
-
- Content
-
-
-
-
-
+ Content
+
`;
diff --git a/packages/terra-form-select/tests/wdio/select-spec.js b/packages/terra-form-select/tests/wdio/select-spec.js
index 2b57bd8b8f2..0f665b41628 100644
--- a/packages/terra-form-select/tests/wdio/select-spec.js
+++ b/packages/terra-form-select/tests/wdio/select-spec.js
@@ -2376,6 +2376,30 @@ Terra.describeViewports('Select', ['tiny'], () => {
it('should display selected option', () => {
Terra.validates.element('tag controlled selected option');
});
+
+ it('should focus deselect on pressing tab key', () => {
+ $('[data-terra-select]').click();
+ $('#terra-select-option-blue').click();
+ $('#terra-select-option-red').click();
+ $('#root').click();
+ browser.keys('Tab');
+ expect($('#terra-tag-deselect-blue')).toBeFocused();
+ browser.keys('Tab');
+ expect($('#terra-tag-deselect-red')).toBeFocused();
+ });
+ });
+ });
+
+ describe('Tag Variant - controlled multiple disabled', () => {
+ before(() => {
+ browser.url('/raw/tests/cerner-terra-core-docs/form-select/control-multiple-disabled');
+ });
+ it('should not focus deselect on pressing tab key if disabled', () => {
+ $('#root').click();
+ browser.keys('Tab');
+ expect($('#terra-tag-deselect-blue')).not.toBeFocused();
+ browser.keys('Tab');
+ expect($('#terra-tag-deselect-red')).not.toBeFocused();
});
});
diff --git a/packages/terra-form-select/translations/de.json b/packages/terra-form-select/translations/de.json
index 04e5549ed9c..80986069007 100644
--- a/packages/terra-form-select/translations/de.json
+++ b/packages/terra-form-select/translations/de.json
@@ -27,5 +27,6 @@
"Terra.form.select.option": "Optionen",
"Terra.form.select.optGroup": "Gruppe {text}",
"Terra.form.select.defaultComboboxDisplay": "Auswählen oder eingeben",
- "Terra.form.select.resultsText": "Ergebnisse mit '{text}'"
+ "Terra.form.select.resultsText": "Ergebnisse mit '{text}'",
+ "Terra.form.select.deselect": "Auswahl aufheben {text}"
}
diff --git a/packages/terra-form-select/translations/en-GB.json b/packages/terra-form-select/translations/en-GB.json
index cae04c8cb5b..feb794c473d 100644
--- a/packages/terra-form-select/translations/en-GB.json
+++ b/packages/terra-form-select/translations/en-GB.json
@@ -27,5 +27,6 @@
"Terra.form.select.option": "Options",
"Terra.form.select.optGroup": "Group {text}",
"Terra.form.select.defaultComboboxDisplay": "Select or Enter",
- "Terra.form.select.resultsText": "Results that contain \"{text}\""
+ "Terra.form.select.resultsText": "Results that contain \"{text}\"",
+ "Terra.form.select.deselect": "Deselect {text}"
}
diff --git a/packages/terra-form-select/translations/en-US.json b/packages/terra-form-select/translations/en-US.json
index d03c91fc4c9..7f470d2ebed 100644
--- a/packages/terra-form-select/translations/en-US.json
+++ b/packages/terra-form-select/translations/en-US.json
@@ -26,5 +26,6 @@
"Terra.form.select.menu": "Menu",
"Terra.form.select.option": "Options",
"Terra.form.select.defaultComboboxDisplay": "Select or Enter",
- "Terra.form.select.resultsText": "Results that contain \"{text}\""
+ "Terra.form.select.resultsText": "Results that contain \"{text}\"",
+ "Terra.form.select.deselect": "Deselect {text}"
}
diff --git a/packages/terra-form-select/translations/en.json b/packages/terra-form-select/translations/en.json
index fbf016d3ada..cf5695b0d16 100644
--- a/packages/terra-form-select/translations/en.json
+++ b/packages/terra-form-select/translations/en.json
@@ -27,5 +27,6 @@
"Terra.form.select.option": "Options",
"Terra.form.select.optGroup": "Group {text}",
"Terra.form.select.defaultComboboxDisplay": "Select or Enter",
- "Terra.form.select.resultsText": "Results that contain \"{text}\""
+ "Terra.form.select.resultsText": "Results that contain \"{text}\"",
+ "Terra.form.select.deselect": "Deselect {text}"
}
diff --git a/packages/terra-form-select/translations/es.json b/packages/terra-form-select/translations/es.json
index 7f2393322fc..b97947557e2 100755
--- a/packages/terra-form-select/translations/es.json
+++ b/packages/terra-form-select/translations/es.json
@@ -27,5 +27,6 @@
"Terra.form.select.option": "Opciones",
"Terra.form.select.optGroup": "Grupo {text}",
"Terra.form.select.defaultComboboxDisplay": "Seleccionar o escribir",
- "Terra.form.select.resultsText":"Resultados que contienen \"{text}\""
+ "Terra.form.select.resultsText": "Resultados que contienen \"{text}\"",
+ "Terra.form.select.deselect": "Deseleccionar {text}"
}
diff --git a/packages/terra-form-select/translations/fr.json b/packages/terra-form-select/translations/fr.json
index c8266894a39..b169edc31ba 100644
--- a/packages/terra-form-select/translations/fr.json
+++ b/packages/terra-form-select/translations/fr.json
@@ -27,5 +27,6 @@
"Terra.form.select.option": "Options",
"Terra.form.select.optGroup": "Groupe {text}",
"Terra.form.select.defaultComboboxDisplay": "Sélectionner ou saisir",
- "Terra.form.select.resultsText": "Résultats contenant « {text} »"
+ "Terra.form.select.resultsText": "Résultats contenant « {text} »",
+ "Terra.form.select.deselect": "Désélectionner {text}"
}
diff --git a/packages/terra-form-select/translations/nl.json b/packages/terra-form-select/translations/nl.json
index 1b2ad90f0d4..b5fe4a9d999 100644
--- a/packages/terra-form-select/translations/nl.json
+++ b/packages/terra-form-select/translations/nl.json
@@ -27,5 +27,6 @@
"Terra.form.select.option": "Opties",
"Terra.form.select.optGroup": "Groeperen {text}",
"Terra.form.select.defaultComboboxDisplay": "Selecteer of druk op Enter",
- "Terra.form.select.resultsText": "Resultaten met \"{text}\""
+ "Terra.form.select.resultsText": "Resultaten met \"{text}\"",
+ "Terra.form.select.deselect": "Deselecteren {text}"
}
diff --git a/packages/terra-form-select/translations/pt.json b/packages/terra-form-select/translations/pt.json
index 4824ba3e9e9..78bf00b3c78 100644
--- a/packages/terra-form-select/translations/pt.json
+++ b/packages/terra-form-select/translations/pt.json
@@ -27,5 +27,6 @@
"Terra.form.select.option": "Opções",
"Terra.form.select.optGroup": "Grupo {text}",
"Terra.form.select.defaultComboboxDisplay": "Selecione ou pressione Enter",
- "Terra.form.select.resultsText": "Resultados que contém \"{text}\""
+ "Terra.form.select.resultsText": "Resultados que contém \"{text}\"",
+ "Terra.form.select.deselect": "Cancelar seleção {text}"
}
diff --git a/packages/terra-form-select/translations/sv.json b/packages/terra-form-select/translations/sv.json
index ac1f7a1724a..0547d9f71fe 100644
--- a/packages/terra-form-select/translations/sv.json
+++ b/packages/terra-form-select/translations/sv.json
@@ -27,5 +27,6 @@
"Terra.form.select.option": "Alternativ",
"Terra.form.select.optGroup": "Grupp {text}",
"Terra.form.select.defaultComboboxDisplay": "Välj eller ange",
- "Terra.form.select.resultsText": "Resultat som innehåller \"{text}\""
+ "Terra.form.select.resultsText": "Resultat som innehåller \"{text}\"",
+ "Terra.form.select.deselect": "Avmarkera {text}"
}