Skip to content

Commit a41ccc6

Browse files
fix(autocomplete-js): query is reflected in the detached search button (#1100)
Co-authored-by: Sarah Dayan <[email protected]>
1 parent 15d9433 commit a41ccc6

File tree

9 files changed

+143
-21
lines changed

9 files changed

+143
-21
lines changed

packages/autocomplete-js/src/__tests__/detached.test.ts

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ describe('detached', () => {
5757

5858
const searchButton = container.querySelector<HTMLButtonElement>(
5959
'.aa-DetachedSearchButton'
60-
);
60+
)!;
6161

6262
// Open detached overlay
6363
searchButton.click();
6464

6565
await waitFor(() => {
66-
const input = document.querySelector<HTMLInputElement>('.aa-Input');
66+
const input = document.querySelector<HTMLInputElement>('.aa-Input')!;
6767

6868
expect(document.querySelector('.aa-DetachedOverlay')).toBeInTheDocument();
6969
expect(document.body).toHaveClass('aa-Detached');
@@ -81,7 +81,7 @@ describe('detached', () => {
8181

8282
const firstItem = document.querySelector<HTMLLIElement>(
8383
'#autocomplete-0-item-0'
84-
);
84+
)!;
8585

8686
// Select the first item
8787
firstItem.click();
@@ -106,7 +106,7 @@ describe('detached', () => {
106106

107107
const searchButton = container.querySelector<HTMLButtonElement>(
108108
'.aa-DetachedSearchButton'
109-
);
109+
)!;
110110

111111
// Open detached overlay
112112
searchButton.click();
@@ -118,7 +118,7 @@ describe('detached', () => {
118118

119119
const cancelButton = document.querySelector<HTMLButtonElement>(
120120
'.aa-DetachedCancelButton'
121-
);
121+
)!;
122122

123123
// Prevent `onTouchStart` event from closing detached overlay
124124
const windowTouchStartListener = jest.fn();
@@ -372,4 +372,98 @@ describe('detached', () => {
372372
);
373373
});
374374
});
375+
376+
test('preserves `query` in the detached search `button` after closing', async () => {
377+
const container = document.createElement('div');
378+
document.body.appendChild(container);
379+
const onStateChange = jest.fn();
380+
autocomplete({
381+
id: 'autocomplete',
382+
detachedMediaQuery: '',
383+
container,
384+
onStateChange,
385+
});
386+
387+
const searchButton = container.querySelector<HTMLButtonElement>(
388+
'.aa-DetachedSearchButton'
389+
)!;
390+
391+
// Open detached overlay
392+
searchButton.click();
393+
394+
// Type a query in the focused input
395+
await waitFor(() => {
396+
const input = document.querySelector<HTMLInputElement>('.aa-Input')!;
397+
398+
expect(document.querySelector('.aa-DetachedOverlay')).toBeInTheDocument();
399+
expect(document.body).toHaveClass('aa-Detached');
400+
expect(input).toHaveFocus();
401+
402+
fireEvent.input(input, { target: { value: 'a' } });
403+
});
404+
405+
// Wait for the panel to open
406+
await waitFor(() => {
407+
expect(
408+
document.querySelector<HTMLElement>('.aa-Panel')
409+
).toBeInTheDocument();
410+
});
411+
412+
const cancelButton = document.querySelector<HTMLButtonElement>(
413+
'.aa-DetachedCancelButton'
414+
)!;
415+
416+
// Close detached overlay
417+
cancelButton.click();
418+
419+
// The detached overlay should close
420+
await waitFor(() => {
421+
expect(
422+
document.querySelector('.aa-DetachedOverlay')
423+
).not.toBeInTheDocument();
424+
expect(document.body).not.toHaveClass('aa-Detached');
425+
});
426+
427+
// The `query` should still be present
428+
expect(onStateChange).toHaveBeenLastCalledWith(
429+
expect.objectContaining({
430+
state: expect.objectContaining({ query: 'a' }),
431+
})
432+
);
433+
434+
// The detached search `button` should contain the `query`
435+
expect(
436+
container.querySelector('.aa-DetachedSearchButtonQuery')
437+
).toHaveTextContent('a');
438+
439+
// The detached search `button` placeholder should be hidden when `query` exists
440+
expect(
441+
container.querySelector('.aa-DetachedSearchButtonPlaceholder')
442+
).toHaveAttribute('hidden');
443+
});
444+
445+
test('reflects the initial `query` in the detached search `button`', async () => {
446+
const container = document.createElement('div');
447+
document.body.appendChild(container);
448+
autocomplete({
449+
id: 'autocomplete',
450+
detachedMediaQuery: '',
451+
container,
452+
initialState: {
453+
query: 'a',
454+
},
455+
});
456+
457+
await waitFor(() => {
458+
// The detached search `button` should have the initial `query`
459+
expect(
460+
container.querySelector('.aa-DetachedSearchButtonQuery')
461+
).toHaveTextContent('a');
462+
463+
// The detached search `button` placeholder should be hidden when `query` exists
464+
expect(
465+
container.querySelector('.aa-DetachedSearchButtonPlaceholder')
466+
).toHaveAttribute('hidden');
467+
});
468+
});
375469
});

packages/autocomplete-js/src/autocomplete.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,6 @@ export function autocomplete<TItem extends BaseItem>(
393393
props.value.core.environment.document.body.classList.remove(
394394
'aa-Detached'
395395
);
396-
autocomplete.value.setQuery('');
397-
autocomplete.value.refresh();
398396
}
399397
});
400398
}

packages/autocomplete-js/src/createAutocompleteDom.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,16 @@ export function createAutocompleteDom<TItem extends BaseItem>({
137137
...panelProps,
138138
});
139139

140+
const detachedSearchButtonQuery = createDomElement('div', {
141+
class: classNames.detachedSearchButtonQuery,
142+
textContent: state.query,
143+
});
144+
const detachedSearchButtonPlaceholder = createDomElement('div', {
145+
class: classNames.detachedSearchButtonPlaceholder,
146+
hidden: Boolean(state.query),
147+
textContent: placeholder,
148+
});
149+
140150
if (__TEST__) {
141151
setProperties(panel, {
142152
'data-testid': 'panel',
@@ -148,17 +158,17 @@ export function createAutocompleteDom<TItem extends BaseItem>({
148158
class: classNames.detachedSearchButtonIcon,
149159
children: [SearchIcon({ environment })],
150160
});
151-
const detachedSearchButtonPlaceholder = createDomElement('div', {
152-
class: classNames.detachedSearchButtonPlaceholder,
153-
textContent: placeholder,
154-
});
155161
const detachedSearchButton = createDomElement('button', {
156162
type: 'button',
157163
class: classNames.detachedSearchButton,
158164
onClick() {
159165
setIsModalOpen(true);
160166
},
161-
children: [detachedSearchButtonIcon, detachedSearchButtonPlaceholder],
167+
children: [
168+
detachedSearchButtonIcon,
169+
detachedSearchButtonPlaceholder,
170+
detachedSearchButtonQuery,
171+
],
162172
});
163173
const detachedCancelButton = createDomElement('button', {
164174
type: 'button',
@@ -188,6 +198,8 @@ export function createAutocompleteDom<TItem extends BaseItem>({
188198
return {
189199
detachedContainer,
190200
detachedOverlay,
201+
detachedSearchButtonQuery,
202+
detachedSearchButtonPlaceholder,
191203
inputWrapper,
192204
input,
193205
root,

packages/autocomplete-js/src/getDefaultOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const defaultClassNames: AutocompleteClassNames = {
3535
detachedSearchButton: 'aa-DetachedSearchButton',
3636
detachedSearchButtonIcon: 'aa-DetachedSearchButtonIcon',
3737
detachedSearchButtonPlaceholder: 'aa-DetachedSearchButtonPlaceholder',
38+
detachedSearchButtonQuery: 'aa-DetachedSearchButtonQuery',
3839
form: 'aa-Form',
3940
input: 'aa-Input',
4041
inputWrapper: 'aa-InputWrapper',

packages/autocomplete-js/src/render.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ export function renderSearchBox<TItem extends BaseItem>({
5858
setProperties(dom.label, { hidden: state.status === 'stalled' });
5959
setProperties(dom.loadingIndicator, { hidden: state.status !== 'stalled' });
6060
setProperties(dom.clearButton, { hidden: !state.query });
61+
setProperties(dom.detachedSearchButtonQuery, {
62+
textContent: state.query,
63+
});
64+
setProperties(dom.detachedSearchButtonPlaceholder, {
65+
hidden: Boolean(state.query),
66+
});
6167
}
6268

6369
export function renderPanel<TItem extends BaseItem>(

packages/autocomplete-js/src/types/AutocompleteClassNames.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type AutocompleteClassNames = {
66
detachedSearchButton: string;
77
detachedSearchButtonIcon: string;
88
detachedSearchButtonPlaceholder: string;
9+
detachedSearchButtonQuery: string;
910
form: string;
1011
input: string;
1112
inputWrapper: string;

packages/autocomplete-js/src/types/AutocompleteDom.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ export type AutocompleteDom = {
1010
panel: HTMLDivElement;
1111
detachedContainer: HTMLDivElement;
1212
detachedOverlay: HTMLDivElement;
13+
detachedSearchButtonQuery: HTMLDivElement;
14+
detachedSearchButtonPlaceholder: HTMLDivElement;
1315
};

packages/autocomplete-theme-classic/src/theme.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,10 +930,23 @@ body {
930930
color: rgba(var(--aa-primary-color-rgb), 1);
931931
cursor: initial;
932932
display: flex;
933+
flex-shrink: 0;
933934
height: 100%;
934935
justify-content: center;
935936
width: calc(var(--aa-icon-size) + var(--aa-spacing));
936937
}
938+
@at-root .aa-DetachedSearchButtonQuery {
939+
color: rgba(var(--aa-text-color-rgb), 1);
940+
line-height: 1.25em;
941+
overflow: hidden;
942+
text-overflow: ellipsis;
943+
white-space: nowrap;
944+
}
945+
@at-root .aa-DetachedSearchButtonPlaceholder {
946+
&[hidden] {
947+
display: none;
948+
}
949+
}
937950
}
938951

939952
// Remove scroll on `body`

yarn.lock

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7554,15 +7554,10 @@ caniuse-api@^3.0.0:
75547554
lodash.memoize "^4.1.2"
75557555
lodash.uniq "^4.5.0"
75567556

7557-
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001179, caniuse-lite@^1.0.30001286:
7558-
version "1.0.30001303"
7559-
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001303.tgz#9b168e4f43ccfc372b86f4bc5a551d9b909c95c9"
7560-
integrity sha512-/Mqc1oESndUNszJP0kx0UaQU9kEv9nNtJ7Kn8AdA0mNnH8eR1cj0kG+NbNuC1Wq/b21eA8prhKRA3bbkjONegQ==
7561-
7562-
caniuse-lite@^1.0.30001161, caniuse-lite@^1.0.30001400:
7563-
version "1.0.30001439"
7564-
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz#ab7371faeb4adff4b74dad1718a6fd122e45d9cb"
7565-
integrity sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A==
7557+
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001161, caniuse-lite@^1.0.30001179, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001400:
7558+
version "1.0.30001458"
7559+
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz"
7560+
integrity sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w==
75667561

75677562
capital-case@^1.0.3, capital-case@^1.0.4:
75687563
version "1.0.4"

0 commit comments

Comments
 (0)