diff --git a/src/multiselect/__tests__/multiselect-embedded.test.tsx b/src/multiselect/__tests__/multiselect-embedded.test.tsx index 046cd31acc..ee3087297b 100644 --- a/src/multiselect/__tests__/multiselect-embedded.test.tsx +++ b/src/multiselect/__tests__/multiselect-embedded.test.tsx @@ -1,9 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import * as React from 'react'; +import React, { useState } from 'react'; import { render } from '@testing-library/react'; +import { KeyCode } from '@cloudscape-design/component-toolkit/internal'; import { createWrapper } from '@cloudscape-design/test-utils-core/dom'; import '../../__a11y__/to-validate-a11y'; @@ -32,12 +33,26 @@ const defaultProps: EmbeddedMultiselectProps = { errorText: 'Error', }; +function StatefulEmbeddedMultiselect(props: EmbeddedMultiselectProps) { + const [selectedOptions, setSelectedOptions] = useState(props.selectedOptions); + return ( + { + props.onChange?.(event); + setSelectedOptions(event.detail.selectedOptions); + }} + /> + ); +} + function renderComponent(props: Partial) { const { container } = render(
- +
); return { container }; @@ -83,13 +98,39 @@ test('ARIA labels', () => { expect(list).toHaveAccessibleDescription('Loading...'); }); -test('highlights first option when list is focused', () => { +test('highlights first option when list is focused and removes highlight when the focus is lost', () => { renderComponent({}); const list = createWrapper().find('ul')!.getElement(); list.focus(); + const highlightedItemsAfterFocus = createWrapper().findAllByClassName(selectableItemsStyles.highlighted); + expect(highlightedItemsAfterFocus).toHaveLength(1); + expect(highlightedItemsAfterFocus[0].getElement()).toHaveTextContent('First'); + + list.blur(); + + const highlightedItemsAfterBlur = createWrapper().findAllByClassName(selectableItemsStyles.highlighted); + expect(highlightedItemsAfterBlur).toHaveLength(0); +}); + +test('selects options with Enter and Space and keeps highlight after Esc is pressed', () => { + renderComponent({}); + + createWrapper().find('ul')!.focus(); + createWrapper().find('ul')!.keydown(KeyCode.enter); + createWrapper().find('ul')!.keydown(KeyCode.down); + createWrapper().find('ul')!.keydown(KeyCode.space); + const highlightedItems = createWrapper().findAllByClassName(selectableItemsStyles.highlighted); + const selectedItems = createWrapper().findAllByClassName(selectableItemsStyles.selected); expect(highlightedItems).toHaveLength(1); - expect(highlightedItems[0].getElement()).toHaveTextContent('First'); + expect(selectedItems).toHaveLength(2); + + createWrapper().find('ul')!.keydown(KeyCode.escape); + + const highlightedItemsAfterEscape = createWrapper().findAllByClassName(selectableItemsStyles.highlighted); + const selectedItemsAfterEscape = createWrapper().findAllByClassName(selectableItemsStyles.selected); + expect(highlightedItemsAfterEscape).toHaveLength(1); + expect(selectedItemsAfterEscape).toHaveLength(2); }); diff --git a/src/select/utils/use-select.ts b/src/select/utils/use-select.ts index 5b5cd6c2ee..9017c829bb 100644 --- a/src/select/utils/use-select.ts +++ b/src/select/utils/use-select.ts @@ -66,7 +66,7 @@ export function useSelect({ const filterRef = useRef(null); const triggerRef = useRef(null); const menuRef = useRef(null); - const hasFilter = filteringType !== 'none'; + const hasFilter = filteringType !== 'none' && !embedded; const activeRef = hasFilter ? filterRef : menuRef; const __selectedOptions = connectOptionsByValue(options, selectedOptions); const __selectedValuesSet = selectedOptions.reduce((selectedValuesSet: Set, item: OptionDefinition) => { @@ -146,8 +146,10 @@ export function useSelect({ goHome: goHomeWithKeyboard, goEnd: goEndWithKeyboard, closeDropdown: () => { - triggerRef.current?.focus(); - closeDropdown(); + if (!embedded) { + triggerRef.current?.focus(); + closeDropdown(); + } }, preventNativeSpace: !hasFilter, }); @@ -228,7 +230,7 @@ export function useSelect({ }, statusType, }; - if (!hasFilter || embedded) { + if (!hasFilter) { menuProps.onKeyDown = activeKeyDownHandler; menuProps.nativeAttributes = { 'aria-activedescendant': highlightedOptionId, @@ -236,7 +238,12 @@ export function useSelect({ } if (embedded) { menuProps.onFocus = () => { - goHomeWithKeyboard(); + if (!highlightedOption) { + goHomeWithKeyboard(); + } + }; + menuProps.onBlur = () => { + resetHighlightWithKeyboard(); }; } return menuProps; @@ -298,13 +305,13 @@ export function useSelect({ ]); useEffect(() => { - if (isOpen) { + if (isOpen && !embedded) { // dropdown-fit calculations ensure that the dropdown will fit inside the current // viewport, so prevent the browser from trying to scroll it into view (e.g. if // scroll-padding-top is set on a parent) activeRef.current?.focus({ preventScroll: true }); } - }, [isOpen, activeRef]); + }, [isOpen, activeRef, embedded]); useForwardFocus(externalRef, triggerRef as React.RefObject); const highlightedGroupSelected =