From 9ad3f137658c15090bc65ab9092e768627fe7e34 Mon Sep 17 00:00:00 2001 From: Avijit Das Date: Wed, 17 Apr 2024 14:45:50 +0530 Subject: [PATCH 01/13] UXPLATFORM-10191 Fixed focus issue in multiselect --- packages/terra-form-select/CHANGELOG.md | 6 + .../terra-form-select/src/MultiSelect.jsx | 2 +- .../clinical-lowlight-theme/Tag.module.scss | 1 + .../src/orion-fusion-theme/Tag.module.scss | 2 + .../terra-form-select/src/shared/_Tag.jsx | 60 ++++++- .../src/shared/_Tag.module.scss | 6 + .../terra-form-select/tests/jest/Tag.test.jsx | 6 +- .../jest/__snapshots__/Tag.test.jsx.snap | 146 ++++++++++++++---- .../tests/wdio/select-spec.js | 11 ++ .../terra-form-select/translations/de.json | 3 +- .../terra-form-select/translations/en-GB.json | 3 +- .../terra-form-select/translations/en-US.json | 3 +- .../terra-form-select/translations/en.json | 3 +- .../terra-form-select/translations/es.json | 3 +- .../terra-form-select/translations/fr.json | 3 +- .../terra-form-select/translations/nl.json | 3 +- .../terra-form-select/translations/pt.json | 3 +- .../terra-form-select/translations/sv.json | 3 +- 18 files changed, 216 insertions(+), 51 deletions(-) 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..337e9d5f24e 100644 --- a/packages/terra-form-select/src/MultiSelect.jsx +++ b/packages/terra-form-select/src/MultiSelect.jsx @@ -192,7 +192,7 @@ class MultiSelect extends React.Component { const selectValue = SelectUtil.value(this.props, this.state); return selectValue.map(tag => ( - + {SelectUtil.valueDisplay(this.props, tag)} )); 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/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..1636880cb07 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,66 @@ 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, }; /* eslint-disable jsx-a11y/no-static-element-interactions */ -const Tag = ({ children, onDeselect, value }) => { +const Tag = ({ + children, onDeselect, value, disabled, intl, +}) => { const theme = React.useContext(ThemeContext); + const tagRef = useRef(null); + + const handleEnterKeyPress = (event) => { + if ((event.key === 'Enter' || event.key === 'Backspace') && !disabled) { + event.stopPropagation(); + onDeselect(value); + const previousLi = tagRef.current.previousElementSibling; + + if (previousLi) { + const deselectElement = previousLi.children[1]; + if (deselectElement) { + deselectElement.focus(); + } + } else { + const nextLi = tagRef.current.nextElementSibling; + const parentUl = tagRef.current.closest('ul'); + if (nextLi) { + const deselectElement = nextLi.children[1]; + if (deselectElement) { + deselectElement.focus(); + } else { + parentUl.click(); + } + } else { + parentUl.click(); + } + } + } + }; return ( -
  • +
  • {children} - { onDeselect(value); }} role="presentation"> + { if (!disabled) onDeselect(value); }} + // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex + tabIndex={!disabled ? 0 : null} + role={!disabled ? 'button' : null} + aria-label={!disabled ? intl.formatMessage({ id: 'Terra.form.select.deselect' }, { text: children }) : null} + >
  • @@ -38,4 +88,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..b0881099d9e 100644 --- a/packages/terra-form-select/tests/wdio/select-spec.js +++ b/packages/terra-form-select/tests/wdio/select-spec.js @@ -2376,6 +2376,17 @@ Terra.describeViewports('Select', ['tiny'], () => { it('should display selected option', () => { Terra.validates.element('tag controlled selected option'); }); + + it('should focus 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(); + }); }); }); diff --git a/packages/terra-form-select/translations/de.json b/packages/terra-form-select/translations/de.json index 04e5549ed9c..a7ae2580df7 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..90d8fa63a83 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}" } From 7e9da3ea3c6e1aafad1cbdb27f4a590937791df8 Mon Sep 17 00:00:00 2001 From: adavijit Date: Wed, 17 Apr 2024 14:53:41 +0530 Subject: [PATCH 02/13] Refactored tabIndex logic --- packages/terra-form-select/src/shared/_Tag.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/terra-form-select/src/shared/_Tag.jsx b/packages/terra-form-select/src/shared/_Tag.jsx index 1636880cb07..d31f22d0be2 100644 --- a/packages/terra-form-select/src/shared/_Tag.jsx +++ b/packages/terra-form-select/src/shared/_Tag.jsx @@ -75,8 +75,7 @@ const Tag = ({ onKeyDown={handleEnterKeyPress} className={cx('deselect')} onClick={() => { if (!disabled) onDeselect(value); }} - // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex - tabIndex={!disabled ? 0 : null} + tabIndex={!disabled ? 0 : -1} role={!disabled ? 'button' : null} aria-label={!disabled ? intl.formatMessage({ id: 'Terra.form.select.deselect' }, { text: children }) : null} > From f2afd039d61db341501bf5d0b3bba8f1301ebe8c Mon Sep 17 00:00:00 2001 From: adavijit Date: Wed, 17 Apr 2024 14:56:36 +0530 Subject: [PATCH 03/13] Refactored area-label logic --- packages/terra-form-select/src/shared/_Tag.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/terra-form-select/src/shared/_Tag.jsx b/packages/terra-form-select/src/shared/_Tag.jsx index d31f22d0be2..4b16a993fa9 100644 --- a/packages/terra-form-select/src/shared/_Tag.jsx +++ b/packages/terra-form-select/src/shared/_Tag.jsx @@ -76,8 +76,8 @@ const Tag = ({ className={cx('deselect')} onClick={() => { if (!disabled) onDeselect(value); }} tabIndex={!disabled ? 0 : -1} - role={!disabled ? 'button' : null} - aria-label={!disabled ? intl.formatMessage({ id: 'Terra.form.select.deselect' }, { text: children }) : null} + role="button" + aria-label={intl.formatMessage({ id: 'Terra.form.select.deselect' }, { text: children })} > From 4f3f9f46e309eb7c4386c86e88292c17582d36b6 Mon Sep 17 00:00:00 2001 From: adavijit Date: Wed, 17 Apr 2024 15:23:35 +0530 Subject: [PATCH 04/13] Modified alert-spec file --- packages/terra-alert/tests/wdio/alert-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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'); }); From d47e0883ef37ee48df3b4148c44e854d3db363ee Mon Sep 17 00:00:00 2001 From: adavijit Date: Thu, 18 Apr 2024 11:56:47 +0530 Subject: [PATCH 05/13] UXPLATFORM-191 Fixed dropdown open issue --- packages/terra-form-select/src/shared/_Tag.jsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/terra-form-select/src/shared/_Tag.jsx b/packages/terra-form-select/src/shared/_Tag.jsx index 4b16a993fa9..56a954cc6cd 100644 --- a/packages/terra-form-select/src/shared/_Tag.jsx +++ b/packages/terra-form-select/src/shared/_Tag.jsx @@ -38,12 +38,12 @@ const Tag = ({ const theme = React.useContext(ThemeContext); const tagRef = useRef(null); - const handleEnterKeyPress = (event) => { + const handleKeyPress = (event) => { if ((event.key === 'Enter' || event.key === 'Backspace') && !disabled) { event.stopPropagation(); onDeselect(value); const previousLi = tagRef.current.previousElementSibling; - + const selectInput = tagRef.current.closest('ul').parentElement.parentElement.children[1].children[0]; if (previousLi) { const deselectElement = previousLi.children[1]; if (deselectElement) { @@ -51,16 +51,15 @@ const Tag = ({ } } else { const nextLi = tagRef.current.nextElementSibling; - const parentUl = tagRef.current.closest('ul'); if (nextLi) { const deselectElement = nextLi.children[1]; if (deselectElement) { deselectElement.focus(); } else { - parentUl.click(); + selectInput.focus(); } } else { - parentUl.click(); + selectInput.focus(); } } } @@ -72,7 +71,7 @@ const Tag = ({ { if (!disabled) onDeselect(value); }} tabIndex={!disabled ? 0 : -1} From 497d4a999a6b9ff51c161b6dff5f141b6efbae6e Mon Sep 17 00:00:00 2001 From: Avijit Das Date: Tue, 23 Apr 2024 11:48:54 +0530 Subject: [PATCH 06/13] Update packages/terra-form-select/translations/es.json Co-authored-by: Supreeth --- packages/terra-form-select/translations/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/terra-form-select/translations/es.json b/packages/terra-form-select/translations/es.json index 90d8fa63a83..b97947557e2 100755 --- a/packages/terra-form-select/translations/es.json +++ b/packages/terra-form-select/translations/es.json @@ -27,6 +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}" } From 4433fb7a4fe6dc3fffac9046a83b98883a263915 Mon Sep 17 00:00:00 2001 From: Avijit Das Date: Tue, 23 Apr 2024 11:51:48 +0530 Subject: [PATCH 07/13] Update packages/terra-form-select/translations/de.json Co-authored-by: Supreeth --- packages/terra-form-select/translations/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/terra-form-select/translations/de.json b/packages/terra-form-select/translations/de.json index a7ae2580df7..80986069007 100644 --- a/packages/terra-form-select/translations/de.json +++ b/packages/terra-form-select/translations/de.json @@ -27,6 +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}" } From 64d924066b0fc20e5a887a3e863391c1a937117a Mon Sep 17 00:00:00 2001 From: Avijit Das Date: Tue, 23 Apr 2024 11:51:59 +0530 Subject: [PATCH 08/13] Update packages/terra-form-select/src/shared/_Tag.jsx Co-authored-by: Supreeth --- packages/terra-form-select/src/shared/_Tag.jsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/terra-form-select/src/shared/_Tag.jsx b/packages/terra-form-select/src/shared/_Tag.jsx index 56a954cc6cd..268f509addd 100644 --- a/packages/terra-form-select/src/shared/_Tag.jsx +++ b/packages/terra-form-select/src/shared/_Tag.jsx @@ -51,13 +51,8 @@ const Tag = ({ } } else { const nextLi = tagRef.current.nextElementSibling; - if (nextLi) { - const deselectElement = nextLi.children[1]; - if (deselectElement) { - deselectElement.focus(); - } else { - selectInput.focus(); - } + if (nextLi && nextLi.children[1]) { + nextLi.children[1].focus(); } else { selectInput.focus(); } From bc45c303e9e3a6c62bd63bb2610cadab67244ff8 Mon Sep 17 00:00:00 2001 From: adavijit Date: Wed, 24 Apr 2024 16:35:11 +0530 Subject: [PATCH 09/13] Refactored tab key feature --- packages/terra-core-docs/CHANGELOG.md | 3 ++ .../ControlMultipleDisabled.test.jsx | 42 +++++++++++++++++++ .../terra-form-select/src/MultiSelect.jsx | 42 ++++++++++++++++++- .../terra-form-select/src/multiple/Frame.jsx | 6 +++ .../terra-form-select/src/shared/_Tag.jsx | 28 +++++++++---- .../tests/wdio/select-spec.js | 15 ++++++- 6 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 packages/terra-core-docs/src/terra-dev-site/test/form-select/ControlMultipleDisabled.test.jsx 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/src/MultiSelect.jsx b/packages/terra-form-select/src/MultiSelect.jsx index 337e9d5f24e..fd761bdf9df 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 }), + ariaHidden: 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({ ariaHidden: true }); } + + handleBlur() { this.setState({ ariaHidden: 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/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/shared/_Tag.jsx b/packages/terra-form-select/src/shared/_Tag.jsx index 268f509addd..eeb6aae5484 100644 --- a/packages/terra-form-select/src/shared/_Tag.jsx +++ b/packages/terra-form-select/src/shared/_Tag.jsx @@ -29,11 +29,22 @@ const propTypes = { * 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 tag should have aria-hidden attribute set to true or false. + * Default is false. + */ + ariaHidden: PropTypes.bool, }; /* eslint-disable jsx-a11y/no-static-element-interactions */ const Tag = ({ - children, onDeselect, value, disabled, intl, + children, onDeselect, value, disabled, intl, inputRef, ariaHidden, }) => { const theme = React.useContext(ThemeContext); const tagRef = useRef(null); @@ -43,19 +54,21 @@ const Tag = ({ event.stopPropagation(); onDeselect(value); const previousLi = tagRef.current.previousElementSibling; - const selectInput = tagRef.current.closest('ul').parentElement.parentElement.children[1].children[0]; if (previousLi) { - const deselectElement = previousLi.children[1]; + const deselectElement = previousLi.querySelector(':scope > :nth-child(2)'); if (deselectElement) { deselectElement.focus(); } } else { const nextLi = tagRef.current.nextElementSibling; - if (nextLi && nextLi.children[1]) { - nextLi.children[1].focus(); - } else { - selectInput.focus(); + if (nextLi) { + const nextFocusableElement = nextLi.querySelector(':scope > :nth-child(2)'); + if (nextFocusableElement) { + nextFocusableElement.focus(); + return; + } } + inputRef.focus(); } } }; @@ -72,6 +85,7 @@ const Tag = ({ tabIndex={!disabled ? 0 : -1} role="button" aria-label={intl.formatMessage({ id: 'Terra.form.select.deselect' }, { text: children })} + aria-hidden={ariaHidden} > diff --git a/packages/terra-form-select/tests/wdio/select-spec.js b/packages/terra-form-select/tests/wdio/select-spec.js index b0881099d9e..0f665b41628 100644 --- a/packages/terra-form-select/tests/wdio/select-spec.js +++ b/packages/terra-form-select/tests/wdio/select-spec.js @@ -2377,7 +2377,7 @@ Terra.describeViewports('Select', ['tiny'], () => { Terra.validates.element('tag controlled selected option'); }); - it('should focus on pressing tab key', () => { + it('should focus deselect on pressing tab key', () => { $('[data-terra-select]').click(); $('#terra-select-option-blue').click(); $('#terra-select-option-red').click(); @@ -2390,6 +2390,19 @@ Terra.describeViewports('Select', ['tiny'], () => { }); }); + 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(); + }); + }); + describe('OptGroup', () => { describe('should render an optgroup', () => { before(() => browser.url('/raw/tests/cerner-terra-core-docs/form-select/opt-group')); From f158e38f4583c6e57f5d4904721fa6bd35d1916d Mon Sep 17 00:00:00 2001 From: adavijit Date: Wed, 24 Apr 2024 16:53:51 +0530 Subject: [PATCH 10/13] Refactored tab key when ariaHidden --- packages/terra-form-select/src/shared/_Tag.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/terra-form-select/src/shared/_Tag.jsx b/packages/terra-form-select/src/shared/_Tag.jsx index eeb6aae5484..01250d41c60 100644 --- a/packages/terra-form-select/src/shared/_Tag.jsx +++ b/packages/terra-form-select/src/shared/_Tag.jsx @@ -82,7 +82,7 @@ const Tag = ({ onKeyDown={handleKeyPress} className={cx('deselect')} onClick={() => { if (!disabled) onDeselect(value); }} - tabIndex={!disabled ? 0 : -1} + tabIndex={!disabled && !ariaHidden ? 0 : -1} role="button" aria-label={intl.formatMessage({ id: 'Terra.form.select.deselect' }, { text: children })} aria-hidden={ariaHidden} From d66efa8a234d732211ba6dfb330833c3f7db2822 Mon Sep 17 00:00:00 2001 From: adavijit Date: Wed, 24 Apr 2024 17:17:51 +0530 Subject: [PATCH 11/13] Refactored tabIndex logic --- packages/terra-form-select/src/shared/_Tag.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/terra-form-select/src/shared/_Tag.jsx b/packages/terra-form-select/src/shared/_Tag.jsx index 01250d41c60..eeb6aae5484 100644 --- a/packages/terra-form-select/src/shared/_Tag.jsx +++ b/packages/terra-form-select/src/shared/_Tag.jsx @@ -82,7 +82,7 @@ const Tag = ({ onKeyDown={handleKeyPress} className={cx('deselect')} onClick={() => { if (!disabled) onDeselect(value); }} - tabIndex={!disabled && !ariaHidden ? 0 : -1} + tabIndex={!disabled ? 0 : -1} role="button" aria-label={intl.formatMessage({ id: 'Terra.form.select.deselect' }, { text: children })} aria-hidden={ariaHidden} From f8075d538a657d85a4cf2d65305cd65f73f5ad59 Mon Sep 17 00:00:00 2001 From: adavijit Date: Wed, 24 Apr 2024 18:39:47 +0530 Subject: [PATCH 12/13] Removed aria-hidden and refactored area-label logic --- packages/terra-form-select/src/MultiSelect.jsx | 8 ++++---- packages/terra-form-select/src/shared/_Tag.jsx | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/terra-form-select/src/MultiSelect.jsx b/packages/terra-form-select/src/MultiSelect.jsx index fd761bdf9df..cb7c0ca2a66 100644 --- a/packages/terra-form-select/src/MultiSelect.jsx +++ b/packages/terra-form-select/src/MultiSelect.jsx @@ -137,7 +137,7 @@ class MultiSelect extends React.Component { this.state = { value: SelectUtil.defaultValue({ defaultValue, value, multiple: true }), - ariaHidden: false, + isInputFocused: false, }; this.inputRef = null; this.display = this.display.bind(this); @@ -196,9 +196,9 @@ class MultiSelect extends React.Component { } } - handleFocus() { this.setState({ ariaHidden: true }); } + handleFocus() { this.setState({ isInputFocused: true }); } - handleBlur() { this.setState({ ariaHidden: false }); } + handleBlur() { this.setState({ isInputFocused: false }); } /** * Receives the reference to the input element from the Frame component. @@ -227,7 +227,7 @@ class MultiSelect extends React.Component { key={tag} onDeselect={this.handleDeselect} disabled={this.props.disabled} - ariaHidden={this.state.ariaHidden} + isInputFocused={this.state.isInputFocused} inputRef={this.inputRef} > {SelectUtil.valueDisplay(this.props, tag)} diff --git a/packages/terra-form-select/src/shared/_Tag.jsx b/packages/terra-form-select/src/shared/_Tag.jsx index eeb6aae5484..499be509247 100644 --- a/packages/terra-form-select/src/shared/_Tag.jsx +++ b/packages/terra-form-select/src/shared/_Tag.jsx @@ -36,15 +36,15 @@ const propTypes = { focus: PropTypes.instanceOf(Element), }), /** - * Specifies whether the tag should have aria-hidden attribute set to true or false. + * Specifies whether the input focus is set to true or false. * Default is false. */ - ariaHidden: PropTypes.bool, + isInputFocused: PropTypes.bool, }; /* eslint-disable jsx-a11y/no-static-element-interactions */ const Tag = ({ - children, onDeselect, value, disabled, intl, inputRef, ariaHidden, + children, onDeselect, value, disabled, intl, inputRef, isInputFocused, }) => { const theme = React.useContext(ThemeContext); const tagRef = useRef(null); @@ -72,6 +72,8 @@ const Tag = ({ } } }; + + const attributes = isInputFocused ? '' : { 'aria-label': intl.formatMessage({ id: 'Terra.form.select.deselect' }, { text: children }) }; return (
  • @@ -84,8 +86,7 @@ const Tag = ({ onClick={() => { if (!disabled) onDeselect(value); }} tabIndex={!disabled ? 0 : -1} role="button" - aria-label={intl.formatMessage({ id: 'Terra.form.select.deselect' }, { text: children })} - aria-hidden={ariaHidden} + {...attributes} > From a55180943f5f14f991208977ef912e33e26cc895 Mon Sep 17 00:00:00 2001 From: adavijit Date: Wed, 24 Apr 2024 19:01:29 +0530 Subject: [PATCH 13/13] Refactored role logic --- packages/terra-form-select/src/shared/_Tag.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/terra-form-select/src/shared/_Tag.jsx b/packages/terra-form-select/src/shared/_Tag.jsx index 499be509247..fe96d2f635a 100644 --- a/packages/terra-form-select/src/shared/_Tag.jsx +++ b/packages/terra-form-select/src/shared/_Tag.jsx @@ -73,7 +73,8 @@ const Tag = ({ } }; - const attributes = isInputFocused ? '' : { 'aria-label': intl.formatMessage({ id: 'Terra.form.select.deselect' }, { text: children }) }; + const attributes = isInputFocused ? { role: 'presentation' } + : { role: 'button', 'aria-label': intl.formatMessage({ id: 'Terra.form.select.deselect' }, { text: children }) }; return (