Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiComboBox] Optional case sensitive option matching #6268

Merged
merged 11 commits into from
Oct 3, 2022
12 changes: 8 additions & 4 deletions src/components/combo_box/combo_box.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -462,18 +462,20 @@ describe('behavior', () => {
});

describe('isCaseSensitive', () => {
const sortMatchesByOptions = [
const isCaseSensitiveOptions = [
{
label: 'Case sensitivity',
},
...options,
];

test('options "false"', () => {
const component = mount<
EuiComboBox<TitanOption>,
EuiComboBoxProps<TitanOption>,
{ matchingOptions: TitanOption[] }
>(<EuiComboBox options={sortMatchesByOptions} isCaseSensitive={false} />);
>(
<EuiComboBox options={isCaseSensitiveOptions} isCaseSensitive={false} />
);

findTestSubject(component, 'comboBoxSearchInput').simulate('change', {
target: { value: 'case' },
Expand All @@ -489,7 +491,9 @@ describe('behavior', () => {
EuiComboBox<TitanOption>,
EuiComboBoxProps<TitanOption>,
{ matchingOptions: TitanOption[] }
>(<EuiComboBox options={sortMatchesByOptions} isCaseSensitive={true} />);
>(
<EuiComboBox options={isCaseSensitiveOptions} isCaseSensitive={true} />
);

findTestSubject(component, 'comboBoxSearchInput').simulate('change', {
target: { value: 'case' },
Expand Down
27 changes: 21 additions & 6 deletions src/components/combo_box/combo_box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
getMatchingOptions,
flattenOptionGroups,
getSelectedOptionForSearchValue,
transformForCaseSensitivity,
SortMatchesBy,
} from './matching_options';
import {
Expand Down Expand Up @@ -497,26 +498,40 @@ export class EuiComboBox<T> extends Component<
if (this.state.matchingOptions.length !== 1) {
return false;
}
return (
this.state.matchingOptions[0].label.toLowerCase() ===
searchValue.toLowerCase()
const normalizedSearchSubject = transformForCaseSensitivity(
this.state.matchingOptions[0].label,
this.props.isCaseSensitive
);
const normalizedSearchValue = transformForCaseSensitivity(
searchValue,
this.props.isCaseSensitive
);
return normalizedSearchSubject === normalizedSearchValue;
};

areAllOptionsSelected = () => {
const { options, selectedOptions, async } = this.props;
const { options, selectedOptions, async, isCaseSensitive } = this.props;
// Assume if this is async then there could be infinite options.
if (async) {
return false;
}

const flattenOptions = flattenOptionGroups(options).map((option) => {
return { ...option, label: option.label.trim().toLowerCase() };
return {
...option,
label: transformForCaseSensitivity(
option.label.trim(),
isCaseSensitive
),
};
});

let numberOfSelectedOptions = 0;
selectedOptions.forEach(({ label }) => {
const trimmedLabel = label.trim().toLowerCase();
const trimmedLabel = transformForCaseSensitivity(
label.trim(),
isCaseSensitive
);
if (
flattenOptions.findIndex((option) => option.label === trimmedLabel) !==
-1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export class EuiComboBoxOptionsList<T> extends Component<

if (isGroupLabelOption) {
return (
<div key={key ?? label.toLowerCase()} style={style}>
<div key={key ?? label} style={style}>
<EuiComboBoxTitle>{label}</EuiComboBoxTitle>
</div>
);
Expand All @@ -244,7 +244,7 @@ export class EuiComboBoxOptionsList<T> extends Component<
return (
<EuiFilterSelectItem
style={style}
key={option.key ?? option.label.toLowerCase()}
key={option.key ?? option.label}
Copy link
Contributor

@cee-chen cee-chen Sep 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we should have actually removed the .toLowerCase()s in this file, since they're unrelated to searching? although I'm also not sure why it was added in the first place

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that if users happen to have the same word with different casing (e.g., Test and test) that this could result in the React identical key warning. Not sure there was any value in having toLowerCase in the first place.

onClick={() => {
if (onOptionClick) {
onOptionClick(option);
Expand Down
39 changes: 25 additions & 14 deletions src/components/combo_box/matching_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ interface GetSelectedOptionForSearchValue<T>
optionKey?: string;
}

export const transformForCaseSensitivity = (
string: string,
isCaseSensitive?: boolean
) => (isCaseSensitive ? string : string.toLowerCase());

export const flattenOptionGroups = <T>(
optionsOrGroups: Array<EuiComboBoxOptionOption<T>>
) => {
Expand All @@ -61,13 +66,15 @@ export const getSelectedOptionForSearchValue = <T>({
selectedOptions,
optionKey,
}: GetSelectedOptionForSearchValue<T>) => {
const normalizedSearchValue = isCaseSensitive
? searchValue
: searchValue.toLowerCase();
const normalizedSearchValue = transformForCaseSensitivity(
searchValue,
isCaseSensitive
);
return selectedOptions.find((option) => {
const normalizedOption = isCaseSensitive
? option.label
: option.label.toLowerCase();
const normalizedOption = transformForCaseSensitivity(
option.label,
isCaseSensitive
);
return (
normalizedOption === normalizedSearchValue &&
(!optionKey || option.key === optionKey)
Expand Down Expand Up @@ -106,8 +113,10 @@ const collectMatchingOption = <T>({
return;
}

let normalizedOption = option.label.trim();
if (!isCaseSensitive) normalizedOption = normalizedOption.toLowerCase();
const normalizedOption = transformForCaseSensitivity(
option.label.trim(),
isCaseSensitive
);
if (normalizedOption.includes(normalizedSearchValue)) {
accumulator.push(option);
}
Expand All @@ -122,9 +131,10 @@ export const getMatchingOptions = <T>({
showPrevSelected = false,
sortMatchesBy = 'none',
}: GetMatchingOptions<T>) => {
let normalizedSearchValue = searchValue.trim();
if (!isCaseSensitive)
normalizedSearchValue = normalizedSearchValue.toLocaleLowerCase();
const normalizedSearchValue = transformForCaseSensitivity(
searchValue.trim(),
isCaseSensitive
);
let matchingOptions: Array<EuiComboBoxOptionOption<T>> = [];

options.forEach((option) => {
Expand Down Expand Up @@ -172,9 +182,10 @@ export const getMatchingOptions = <T>({
} = { startWith: [], others: [] };

matchingOptions.forEach((object) => {
const normalizedLabel = isCaseSensitive
? object.label
: object.label.toLowerCase();
const normalizedLabel = transformForCaseSensitivity(
object.label,
isCaseSensitive
);
if (normalizedLabel.startsWith(normalizedSearchValue)) {
refObj.startWith.push(object);
} else {
Expand Down