diff --git a/ui/src/dropdown.test.tsx b/ui/src/dropdown.test.tsx
index 833f33ad2f..8d53958b9a 100644
--- a/ui/src/dropdown.test.tsx
+++ b/ui/src/dropdown.test.tsx
@@ -880,4 +880,88 @@ describe('Dropdown.tsx', () => {
})
})
})
-})
\ No newline at end of file
+
+ describe('Dialog dropdown exact', () => {
+ let dialogProps: Dropdown
+
+ const createChoices = (size: number) => Array.from(Array(size).keys()).map(key => ({ name: String(key), label: `Choice ${key}` }))
+ const overOneHundredChoices = createChoices(101)
+ const choices = createChoices(10)
+
+ beforeEach(() => {
+ dialogProps = {
+ ...defaultProps,
+ popup: 'always',
+ exactSearch: true,
+ choices
+ };
+ });
+ it('Sets correct args after exact filter', () => {
+ const { getByText, getByTestId, getAllByRole } = render()
+
+ fireEvent.click(getByTestId(name))
+ fireEvent.change(getByTestId(`${name}-search`), { target: { value: 'Choice 9' } })
+ fireEvent.click(getAllByRole('checkbox')[0])
+ fireEvent.click(getByText('Select'))
+
+ expect(wave.args[name]).toMatchObject(['1', '9'])
+ });
+ it('Filters exact correctly', () => {
+ const { getByTestId, getAllByRole } = render()
+
+ fireEvent.click(getByTestId(name))
+ expect(getAllByRole('listitem')).toHaveLength(10)
+ fireEvent.change(getByTestId(`${name}-search`), { target: { value: 'Choice 9' } })
+ expect(getAllByRole('listitem')).toHaveLength(1)
+ });
+
+ it('Filters correctly - reset filter', () => {
+ const { getByTestId, getAllByRole } = render()
+
+ fireEvent.click(getByTestId(name))
+ expect(getAllByRole('listitem')).toHaveLength(10)
+ fireEvent.change(getByTestId(`${name}-search`), { target: { value: 'Choice 9' } })
+ expect(getAllByRole('listitem')).toHaveLength(1)
+
+ fireEvent.change(getByTestId(`${name}-search`), { target: { value: '' } })
+ expect(getAllByRole('listitem')).toHaveLength(10)
+ });
+
+ it('Resets filtered items on cancel', () => {
+ const { getByTestId, getAllByRole, getByText } = render()
+
+ fireEvent.click(getByTestId(name))
+ expect(getAllByRole('listitem')).toHaveLength(10)
+ fireEvent.change(getByTestId(`${name}-search`), { target: { value: 'Choice 9' } })
+ expect(getAllByRole('listitem')).toHaveLength(1)
+ fireEvent.click(getByText('Cancel'))
+ fireEvent.click(getByTestId(name))
+ expect(getAllByRole('listitem')).toHaveLength(10)
+ });
+
+ it('Resets filtered items on submit', () => {
+ const { getByTestId, getAllByRole, getByText } = render()
+
+ fireEvent.click(getByTestId(name))
+ expect(getAllByRole('listitem')).toHaveLength(10)
+ fireEvent.change(getByTestId(`${name}-search`), { target: { value: 'Choice 9' } })
+ expect(getAllByRole('listitem')).toHaveLength(1)
+ fireEvent.click(getByText('Select'))
+ fireEvent.click(getByTestId(name))
+ expect(getAllByRole('listitem')).toHaveLength(10)
+ });
+
+ it('Resets filtered items on single valued submit', () => {
+ const { getByTestId, getAllByRole } = render()
+
+ fireEvent.click(getByTestId(name))
+ expect(getAllByRole('listitem')).toHaveLength(10)
+ fireEvent.change(getByTestId(`${name}-search`), { target: { value: 'Choice 9' } })
+ expect(getAllByRole('listitem')).toHaveLength(1)
+ fireEvent.click(getAllByRole('checkbox')[0])
+ fireEvent.click(getByTestId(name))
+ expect(getAllByRole('listitem')).toHaveLength(10)
+ });
+
+ })
+})
diff --git a/ui/src/dropdown.tsx b/ui/src/dropdown.tsx
index b013578093..b0146f8c1c 100644
--- a/ui/src/dropdown.tsx
+++ b/ui/src/dropdown.tsx
@@ -17,7 +17,7 @@ import { B, Id, S, U } from './core'
import React from 'react'
import { stylesheet } from 'typestyle'
import { Choice } from './choice_group'
-import { fuzzysearch } from './parts/utils'
+import { fuzzysearch, exactsearch } from './parts/utils'
import { clas, cssVar, pc, px } from './theme'
import { wave } from './ui'
@@ -61,6 +61,8 @@ export interface Dropdown {
tooltip?: S
/** Whether to present the choices using a pop-up dialog. By default pops up a dialog only for more than 100 choices. Defaults to 'auto'. */
popup?: 'auto' | 'always' | 'never'
+ /**Whether the search will be exact or fuzzy */
+ exactSearch?: B
}
type DropdownItem = {
@@ -170,10 +172,19 @@ const
getPageSpecification = () => ({ itemCount: PAGE_SIZE, height: ROW_HEIGHT * PAGE_SIZE } as Fluent.IPageSpecification),
choicesToItems = (choices: Choice[] = [], v?: S | S[]) => choices.map(({ name, label, disabled = false }, idx) =>
({ name, text: label || name, idx, checked: Array.isArray(v) ? v.includes(name) : v === name, show: true, disabled })),
- useItems = (choices?: Choice[], v?: S | S[]) => {
+ useItems = (choices?: Choice[], v?: S | S[], exactSearch?: boolean) => {
const [items, setItems] = React.useState(choicesToItems(choices, v))
- const onSearchChange = (_e?: React.ChangeEvent, newVal = '') => setItems(items => items.map(i => ({ ...i, show: fuzzysearch(i.text, newVal) })))
+ const onSearchChange = (_e?: React.ChangeEvent, newVal = '') => {
+ setItems((items) =>
+ items.map((i) => ({
+ ...i,
+ show: exactSearch
+ ? exactsearch(i.text, newVal) // Assuming exactsearch is a function for exact matching
+ : fuzzysearch(i.text, newVal), // Assuming fuzzysearch is a function for fuzzy matching
+ }))
+ );
+ };
return [items, setItems, onSearchChange] as const
},
onRenderCell = (onChecked: any) => (item?: DropdownItem) => item
@@ -323,4 +334,4 @@ export const XDropdown = ({ model: m }: { model: Dropdown }) =>
?
: (m.choices?.length || 0) > 100
?
- :
\ No newline at end of file
+ :
diff --git a/ui/src/parts/utils.ts b/ui/src/parts/utils.ts
index b4b2ef7b96..84e3db06b9 100644
--- a/ui/src/parts/utils.ts
+++ b/ui/src/parts/utils.ts
@@ -24,6 +24,14 @@ export function fuzzysearch(haystack: S, needle: S) {
}
return true
}
+// parts / utils.ts
+export const exactsearch = (searchTerm: string, itemText: string): boolean => {
+
+ // Convert both strings to lowercase for case-insensitive comparison
+ return itemText.toLowerCase() === searchTerm.toLowerCase(); // Exact match
+};
+
+
// https://github.com/h2oai/wave/issues/1395.
export const fixMenuOverflowStyles: Partial = {
@@ -32,4 +40,4 @@ export const fixMenuOverflowStyles: Partial = {
'.ms-ContextualMenu-link': { lineHeight: 'unset' },
'.ms-ContextualMenu-submenuIcon': { lineHeight: 'unset', display: 'flex', alignItems: 'center' },
}
-}
\ No newline at end of file
+}