Skip to content

Commit ac8d58c

Browse files
authored
feat(Forms): add async Autocomplete support to Field.Selection (#4664)
Replaces #4657
1 parent eeee04f commit ac8d58c

File tree

3 files changed

+196
-6
lines changed

3 files changed

+196
-6
lines changed

packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx

+33
Original file line numberDiff line numberDiff line change
@@ -2951,6 +2951,39 @@ describe('Autocomplete component', () => {
29512951
}
29522952
})
29532953

2954+
it('should support "preventSelection"', async () => {
2955+
render(<Autocomplete data={mockData} prevent_selection />)
2956+
2957+
const input = document.querySelector('input')
2958+
await userEvent.type(input, 'aa')
2959+
2960+
expect(input).toHaveValue('aa')
2961+
2962+
{
2963+
const options = document.querySelectorAll('[role="option"]')
2964+
expect(options[0]).toHaveTextContent('AA c')
2965+
expect(options[1]).toHaveTextContent('Vis alt')
2966+
2967+
await userEvent.click(options[0])
2968+
}
2969+
2970+
expect(
2971+
document.querySelector('.dnb-drawer-list__option--selected')
2972+
).not.toBeInTheDocument()
2973+
2974+
await userEvent.click(input)
2975+
2976+
expect(
2977+
document.querySelector('.dnb-drawer-list__option--selected')
2978+
).not.toBeInTheDocument()
2979+
2980+
{
2981+
const options = document.querySelectorAll('[role="option"]')
2982+
expect(options[0]).toHaveTextContent('AA c')
2983+
expect(options[1]).toHaveTextContent('Vis alt')
2984+
}
2985+
})
2986+
29542987
describe('input blur', () => {
29552988
const mainElement = () => document.querySelector('.dnb-autocomplete')
29562989
const inputElement = () =>

packages/dnb-eufemia/src/extensions/forms/Field/Selection/Selection.tsx

+33-6
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ function Selection(props: Props) {
137137
data,
138138
dataPath,
139139
children,
140+
additionalArgs,
140141

141142
// - Autocomplete and Dropdown specific props
142143
autocompleteProps,
@@ -149,13 +150,14 @@ function Selection(props: Props) {
149150
dataList = getValueByPath(dataPath)
150151
}
151152

152-
const handleDropdownChange = useCallback(
153-
({ data }) => {
154-
const selectedKey = data?.selectedKey
153+
const handleDrawerListChange = useCallback(
154+
({ data, value }) => {
155+
const selectedKey = data?.selectedKey ?? value
155156
handleChange?.(
156157
!selectedKey || selectedKey === clearValue
157158
? emptyValue
158-
: selectedKey
159+
: selectedKey,
160+
{ data }
159161
)
160162
},
161163
[handleChange, emptyValue, clearValue]
@@ -200,6 +202,20 @@ function Selection(props: Props) {
200202
...pickSpacingProps(props),
201203
}
202204

205+
const onType = props?.autocompleteProps?.onType
206+
const onTypeAutocompleteHandler = useCallback(
207+
(event) => {
208+
const { value } = event
209+
if (typeof onType === 'function') {
210+
onType(value === undefined ? emptyValue : value, {
211+
...event,
212+
...additionalArgs,
213+
})
214+
}
215+
},
216+
[additionalArgs, emptyValue, onType]
217+
)
218+
203219
switch (variant) {
204220
case 'radio':
205221
case 'button': {
@@ -270,7 +286,7 @@ function Selection(props: Props) {
270286
...htmlAttributes,
271287
data,
272288
size,
273-
on_change: handleDropdownChange,
289+
on_change: handleDrawerListChange,
274290
on_show: handleShow,
275291
on_hide: handleHide,
276292
stretch: true,
@@ -291,9 +307,20 @@ function Selection(props: Props) {
291307
{...sharedProps}
292308
{...(autocompleteProps
293309
? (convertCamelCaseProps(
294-
autocompleteProps
310+
Object.freeze(autocompleteProps)
295311
) as AutocompleteAllProps)
296312
: null)}
313+
value={
314+
autocompleteProps?.preventSelection ? undefined : value
315+
}
316+
on_type={onTypeAutocompleteHandler}
317+
data={
318+
!props.data &&
319+
!props.dataPath &&
320+
autocompleteProps?.mode === 'async'
321+
? undefined
322+
: data
323+
}
297324
/>
298325
) : (
299326
<Dropdown

packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx

+130
Original file line numberDiff line numberDiff line change
@@ -1603,6 +1603,136 @@ describe('variants', () => {
16031603
).toBe(document.querySelector('.dnb-tooltip__content').id)
16041604
})
16051605

1606+
it('should support "onType"', async () => {
1607+
const onType = jest.fn()
1608+
1609+
render(
1610+
<Field.Selection
1611+
variant="autocomplete"
1612+
autocompleteProps={{
1613+
onType,
1614+
}}
1615+
>
1616+
<Field.Option value="foo">Foo</Field.Option>
1617+
<Field.Option value="bar">Bar</Field.Option>
1618+
</Field.Selection>
1619+
)
1620+
1621+
const input = document.querySelector('input')
1622+
await userEvent.type(input, 'foo')
1623+
1624+
expect(onType).toHaveBeenCalledTimes(3)
1625+
expect(onType).toHaveBeenLastCalledWith(
1626+
'foo',
1627+
expect.objectContaining({
1628+
updateData: expect.any(Function),
1629+
dataContext: expect.any(Object),
1630+
})
1631+
)
1632+
})
1633+
1634+
it('should support "preventSelection"', async () => {
1635+
const onChange = jest.fn()
1636+
1637+
render(
1638+
<Field.Selection
1639+
variant="autocomplete"
1640+
autocompleteProps={{
1641+
preventSelection: true,
1642+
}}
1643+
onChange={onChange}
1644+
>
1645+
<Field.Option value="foo">Foo</Field.Option>
1646+
<Field.Option value="bar">Bar</Field.Option>
1647+
</Field.Selection>
1648+
)
1649+
1650+
const input = document.querySelector('input')
1651+
await userEvent.type(input, 'foo')
1652+
1653+
expect(input).toHaveValue('foo')
1654+
1655+
{
1656+
const options = document.querySelectorAll('[role="option"]')
1657+
expect(options[0]).toHaveTextContent('Foo')
1658+
expect(options[1]).toHaveTextContent('Vis alt')
1659+
1660+
await userEvent.click(options[0])
1661+
}
1662+
1663+
expect(onChange).toHaveBeenCalledTimes(1)
1664+
expect(onChange).toHaveBeenLastCalledWith('foo', expect.anything())
1665+
expect(
1666+
document.querySelector('.dnb-drawer-list__option--selected')
1667+
).not.toBeInTheDocument()
1668+
expect(input).toHaveValue('foo')
1669+
1670+
await userEvent.click(input)
1671+
1672+
expect(
1673+
document.querySelector('.dnb-drawer-list__option--selected')
1674+
).not.toBeInTheDocument()
1675+
expect(input).toHaveValue('foo')
1676+
1677+
{
1678+
const options = document.querySelectorAll('[role="option"]')
1679+
expect(options[0]).toHaveTextContent('Foo')
1680+
expect(options[1]).toHaveTextContent('Vis alt')
1681+
}
1682+
1683+
expect(onChange).toHaveBeenCalledTimes(1)
1684+
})
1685+
1686+
describe('mode="async"', () => {
1687+
it('should open DrawerList when focused and data is set with updateData', async () => {
1688+
const onType = jest.fn((value, { updateData }) => {
1689+
updateData([
1690+
{
1691+
selectedKey: 'foo',
1692+
content: 'Foo',
1693+
},
1694+
{
1695+
selectedKey: 'bar',
1696+
content: 'Bar',
1697+
},
1698+
])
1699+
})
1700+
1701+
render(
1702+
<Field.Selection
1703+
variant="autocomplete"
1704+
autocompleteProps={{
1705+
mode: 'async',
1706+
onType,
1707+
}}
1708+
/>
1709+
)
1710+
1711+
const input = document.querySelector('input')
1712+
await userEvent.type(input, 'foo')
1713+
1714+
{
1715+
const options = document.querySelectorAll('[role="option"]')
1716+
expect(options[0]).toHaveTextContent('Foo')
1717+
expect(options[1]).toHaveTextContent('Vis alt')
1718+
expect(input).toHaveValue('foo')
1719+
1720+
await userEvent.click(options[0])
1721+
}
1722+
1723+
expect(input).toHaveValue('Foo')
1724+
1725+
await userEvent.click(input)
1726+
1727+
{
1728+
const options = document.querySelectorAll('[role="option"]')
1729+
expect(options[0]).toHaveTextContent('Foo')
1730+
expect(options[1]).toHaveTextContent('Bar')
1731+
expect(input).toHaveValue('Foo')
1732+
}
1733+
})
1734+
})
1735+
16061736
it('should disable autocomplete', () => {
16071737
render(
16081738
<Field.Selection variant="autocomplete" disabled>

0 commit comments

Comments
 (0)