Skip to content

Commit a6bffc2

Browse files
Several MultiSelect improvements
- `searchListPredicate` property: Allows to filter the complete list of search options at once. - Following optional BlueprintJs properties are forwarded now to override default behaviour: `noResults`, `createNewItemRenderer` and `itemRenderer` - by default, if no searchPredicate or searchListPredicate is defined, the filtering is done via case-insensitive multi-word filtering. - `searchPreficate`: replaced by the, in some cases, more efficient `searchListPredicate`
1 parent aff9553 commit a6bffc2

2 files changed

Lines changed: 53 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
1818
- `useOnly` property: specify if only parts of the content should be used for the shortened preview, this property replaces `firstNonEmptyLineOnly`
1919
- `<ContentBlobToggler />`
2020
- `forceInline` property: force inline rendering
21-
- `<MultiSuggestField />`:
22-
- `isValidNewOption` property: Checks if an input string is or can be turned into a valid new option.
2321
- `<ContextMenu />`
2422
- `togglerSize`: replaces the deprecated `togglerLarge` property
23+
- `<MultiSelect />:
24+
- `searchListPredicate` property: Allows to filter the complete list of search options at once.
25+
- Following optional BlueprintJs properties are forwarded now to override default behaviour: `noResults`, `createNewItemRenderer` and `itemRenderer`
26+
- `isValidNewOption` property: Checks if an input string is or can be turned into a valid new option.
2527

2628
### Fixed
2729

@@ -32,7 +34,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
3234
- take Markdown rendering into account before testing the maximum preview length
3335
- `<NodeContent />`
3436
- header-menu items are vertically centered now
35-
- `<MultiSuggestField />`:
37+
- `<MultiSelect />`:
3638
- border of the BlueprintJS `Tag` elements were fixed
3739

3840
### Changed
@@ -47,13 +49,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
4749
- `<FlexibleLayoutItem />`
4850
- `<GridColumn />`
4951
- `<PropertyName />` and `<PropertyValue />`
52+
- `<MultiSelect />`
53+
- by default, if no searchPredicate or searchListPredicate is defined, the filtering is done via case-insensitive multi-word filtering.
5054

5155
### Deprecated
5256

5357
- `<StringPreviewContentBlobToggler />`
5458
- `firstNonEmptyLineOnly` will be removed, is replaced by `useOnly="firstNonEmptyLine"`
5559
- `<ContextMenu />`
5660
- `togglerLarge`: replaced by the more versatile `togglerSize` property
61+
- `<MultiSelect />`
62+
- `searchPreficate`: replaced by the, in some cases, more efficient `searchListPredicate`
5763

5864
## [25.0.0] - 2025-12-01
5965

src/components/MultiSelect/MultiSelect.tsx

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,26 @@ import { removeExtraSpaces } from "../../common/utils/stringUtils";
1010
import { CLASSPREFIX as eccgui } from "../../configuration/constants";
1111
import { TestableComponent } from "../interfaces";
1212

13-
import { ContextOverlayProps, Highlighter, IconButton, MenuItem, OverflowText, Spinner } from "./../../index";
13+
import {
14+
ContextOverlayProps,
15+
Highlighter,
16+
highlighterUtils,
17+
IconButton,
18+
MenuItem,
19+
OverflowText,
20+
Spinner
21+
} from "./../../index";
1422

1523
export interface MultiSuggestFieldSelectionProps<T> {
1624
newlySelected?: T;
1725
selectedItems: T[];
1826
createdItems: Partial<T>[];
1927
}
2028

21-
interface MultiSuggestFieldCommonProps<T>
29+
export interface MultiSuggestFieldCommonProps<T>
2230
extends TestableComponent,
23-
Pick<BlueprintMultiSelectProps<T>, "items" | "placeholder" | "openOnKeyDown"> {
31+
Pick<BlueprintMultiSelectProps<T>, "items" | "placeholder" | "openOnKeyDown" | "noResults" | "createNewItemRenderer">,
32+
Partial<Pick<BlueprintMultiSelectProps<T>, "itemRenderer">> {
2433
/**
2534
* Additional class name, space separated.
2635
*/
@@ -105,9 +114,20 @@ interface MultiSuggestFieldCommonProps<T>
105114
wrapperProps?: React.HTMLAttributes<HTMLDivElement>;
106115
/**
107116
* Function that allows us to filter values from the option list.
108-
* If not provided, values are filtered by their labels
117+
*
118+
* @deprecated (v26) use `searchListPredicate` instead.
109119
*/
110120
searchPredicate?: (item: T, query: string) => boolean;
121+
122+
/**
123+
* Returns the filtered the search option list.
124+
* By default, a case-insensitive multi-word filtering is applied.
125+
*
126+
* @param items The options.
127+
* @param query The search query.
128+
*/
129+
searchListPredicate?: (items: T[], query: string) => T[]
130+
111131
/**
112132
* Limits the height of the input target plus its dropdown menu when it is opened.
113133
* Need to be a `number not greater than 100` (as `vh`, a unit describing a length relative to the viewport height) or `true` (equals 100).
@@ -169,13 +189,14 @@ export function MultiSuggestField<T>({
169189
"data-testid": dataTestid,
170190
wrapperProps,
171191
searchPredicate,
192+
searchListPredicate,
172193
limitHeightOpened,
173194
intent,
174195
...otherMultiSelectProps
175196
}: MultiSuggestFieldProps<T>) {
176197
// Options created by a user
177198
const createdItems = useRef<T[]>([]);
178-
// Options passed ouside (f.e. from the backend)
199+
// Options passed outside (f.e. from the backend)
179200
const [externalItems, setExternalItems] = React.useState<T[]>([...items]);
180201
// All options (created and passed) that match the query
181202
const [filteredItems, setFilteredItems] = React.useState<T[]>([]);
@@ -267,9 +288,14 @@ export function MultiSuggestField<T>({
267288
setSelectedItems(filteredItems);
268289
};
269290

270-
const defaultFilterPredicate = (item: T, query: string) => {
271-
return itemLabel(item).toLowerCase().includes(query);
272-
};
291+
/** Does a case-insensitive multi-word search in the item label. */
292+
const defaultSearchListPredicate = (items: T[], query: string): T[] => {
293+
const searchWords = highlighterUtils.extractSearchWords(query, true);
294+
return items.filter(item => {
295+
const searchIn = itemLabel(item).toLowerCase()
296+
return highlighterUtils.matchesAllWords(searchIn, searchWords);
297+
})
298+
}
273299

274300
/**
275301
* selects and deselects an item from selection list
@@ -308,10 +334,17 @@ export function MultiSuggestField<T>({
308334
if (requestState.current.query === query) {
309335
// Only use most recent request
310336
const outsideOptions = [...(resultFromQuery ?? externalItems)];
311-
const filter = searchPredicate ?? defaultFilterPredicate;
337+
let itemFilter = defaultSearchListPredicate
338+
if(searchListPredicate) {
339+
itemFilter = searchListPredicate
340+
} else if(searchPredicate) {
341+
itemFilter = (items, query) => {
342+
return items.filter((item) => searchPredicate(item, query))
343+
}
344+
}
312345

313346
setFilteredItems(
314-
[...outsideOptions, ...createdItems.current].filter((item) => filter(item, query.toLowerCase()))
347+
itemFilter([...outsideOptions, ...createdItems.current], query)
315348
);
316349
setShowSpinner(false);
317350
}
@@ -468,7 +501,6 @@ export function MultiSuggestField<T>({
468501
? "Search for item, or enter term to create new one..."
469502
: undefined
470503
}
471-
{...otherMultiSelectProps}
472504
query={requestState.current.query}
473505
onQueryChange={onQueryChange}
474506
items={filteredItems}
@@ -537,6 +569,7 @@ export function MultiSuggestField<T>({
537569
: undefined,
538570
} as BlueprintMultiSelectProps<T>["popoverContentProps"]
539571
}
572+
{...otherMultiSelectProps}
540573
/>
541574
);
542575

0 commit comments

Comments
 (0)