-
Notifications
You must be signed in to change notification settings - Fork 840
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
feat: Add optionMatcher
prop to EuiSelectable
and EuiComboBox
components
#7709
Changes from 5 commits
e28fcf9
e293461
00729ae
0f08ac1
835bac3
b0b9b19
6cc9f8a
d718bf7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
- Added a new, optional `optionMatcher` prop to `EuiSelectable` and `EuiComboBox` allowing passing a custom option matcher function to these components and controlling option filtering for given search string |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,13 +6,14 @@ | |
* Side Public License, v 1. | ||
*/ | ||
|
||
import { EuiComboBoxOptionOption } from './types'; | ||
import { EuiComboBoxOptionOption, EuiComboBoxOptionMatcher } from './types'; | ||
|
||
export type SortMatchesBy = 'none' | 'startsWith'; | ||
interface GetMatchingOptions<T> { | ||
options: Array<EuiComboBoxOptionOption<T>>; | ||
selectedOptions: Array<EuiComboBoxOptionOption<T>>; | ||
searchValue: string; | ||
optionMatcher: EuiComboBoxOptionMatcher<T>; | ||
isCaseSensitive?: boolean; | ||
isPreFiltered?: boolean; | ||
showPrevSelected?: boolean; | ||
|
@@ -21,7 +22,11 @@ interface GetMatchingOptions<T> { | |
interface CollectMatchingOption<T> | ||
extends Pick< | ||
GetMatchingOptions<T>, | ||
'isCaseSensitive' | 'isPreFiltered' | 'showPrevSelected' | ||
| 'isCaseSensitive' | ||
| 'isPreFiltered' | ||
| 'showPrevSelected' | ||
| 'optionMatcher' | ||
| 'searchValue' | ||
> { | ||
accumulator: Array<EuiComboBoxOptionOption<T>>; | ||
option: EuiComboBoxOptionOption<T>; | ||
|
@@ -86,10 +91,12 @@ const collectMatchingOption = <T>({ | |
accumulator, | ||
option, | ||
selectedOptions, | ||
searchValue, | ||
normalizedSearchValue, | ||
isCaseSensitive, | ||
isPreFiltered, | ||
showPrevSelected, | ||
optionMatcher, | ||
}: CollectMatchingOption<T>) => { | ||
// Only show options which haven't yet been selected unless requested. | ||
const selectedOption = getSelectedOptionForSearchValue({ | ||
|
@@ -113,11 +120,13 @@ const collectMatchingOption = <T>({ | |
return; | ||
} | ||
|
||
const normalizedOption = transformForCaseSensitivity( | ||
option.label.trim(), | ||
isCaseSensitive | ||
); | ||
if (normalizedOption.includes(normalizedSearchValue)) { | ||
const isMatching = optionMatcher({ | ||
option, | ||
searchValue, | ||
normalizedSearchValue, | ||
isCaseSensitive: isCaseSensitive ?? true, | ||
}); | ||
if (isMatching) { | ||
accumulator.push(option); | ||
} | ||
}; | ||
|
@@ -126,6 +135,7 @@ export const getMatchingOptions = <T>({ | |
options, | ||
selectedOptions, | ||
searchValue, | ||
optionMatcher, | ||
isCaseSensitive = false, | ||
isPreFiltered = false, | ||
showPrevSelected = false, | ||
|
@@ -145,10 +155,12 @@ export const getMatchingOptions = <T>({ | |
accumulator: matchingOptionsForGroup, | ||
option: groupOption, | ||
selectedOptions, | ||
searchValue, | ||
normalizedSearchValue, | ||
isCaseSensitive, | ||
isPreFiltered, | ||
showPrevSelected, | ||
optionMatcher, | ||
}); | ||
}); | ||
if (matchingOptionsForGroup.length > 0) { | ||
|
@@ -167,10 +179,12 @@ export const getMatchingOptions = <T>({ | |
accumulator: matchingOptions, | ||
option, | ||
selectedOptions, | ||
searchValue, | ||
normalizedSearchValue, | ||
isCaseSensitive, | ||
isPreFiltered, | ||
showPrevSelected, | ||
optionMatcher, | ||
}); | ||
} | ||
}); | ||
|
@@ -197,3 +211,24 @@ export const getMatchingOptions = <T>({ | |
|
||
return matchingOptions; | ||
}; | ||
|
||
/** | ||
* Partial string equality option matcher for EuiComboBox. | ||
* It matches all options with labels including the searched string. | ||
*/ | ||
export const createPartialStringEqualityOptionMatcher = < | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a factory function to keep |
||
TOption | ||
>(): EuiComboBoxOptionMatcher<TOption> => { | ||
return ({ option, isCaseSensitive, normalizedSearchValue }) => { | ||
if (!normalizedSearchValue) { | ||
return true; | ||
} | ||
|
||
const normalizedOption = transformForCaseSensitivity( | ||
option.label.trim(), | ||
isCaseSensitive | ||
); | ||
|
||
return normalizedOption.includes(normalizedSearchValue); | ||
}; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗒️ A general note, mainly for myself here:
Whenever we align on the
code-snippet
API in this PR and merge it, this story needs to be updated as it's a case of "additional composition wrapper" (it will likely need theparameters.codeSnippet.resolveChildren: true
as otherwise this story snippet would be<Render anyStoryArgsHere />
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the case for many of our stories, though, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some for sure. There are different cases I've seen so far. It depends on what should be output per story.
E.g. for cases like stateful wrappers that return just the actual story component it's different than a wrapper that returns the story component as nested child with other composition elements.
For stateful wrappers or related wrappers (Where a parent-subcomponent structure is required and can be determined based on naming it's already done automatically)
For anything else it might need adjustments.
I'm currently checking the newly added stories and there are some new cases as well that I did not consider yet (render functions 🙈)