Skip to content
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

fix(autocomplete-js): query is reflected in the detached search button #1100

Merged
merged 6 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 151 additions & 5 deletions packages/autocomplete-js/src/__tests__/detached.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ describe('detached', () => {

const searchButton = container.querySelector<HTMLButtonElement>(
'.aa-DetachedSearchButton'
);
)!;

// Open detached overlay
searchButton.click();

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

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

const firstItem = document.querySelector<HTMLLIElement>(
'#autocomplete-0-item-0'
);
)!;

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

const searchButton = container.querySelector<HTMLButtonElement>(
'.aa-DetachedSearchButton'
);
)!;

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

const cancelButton = document.querySelector<HTMLButtonElement>(
'.aa-DetachedCancelButton'
);
)!;

// Prevent `onTouchStart` event from closing detached overlay
const windowTouchStartListener = jest.fn();
Expand Down Expand Up @@ -372,4 +372,150 @@ describe('detached', () => {
);
});
});

test('preserves `query` after closing', async () => {
const container = document.createElement('div');
document.body.appendChild(container);
const onStateChange = jest.fn();
const { setQuery } = autocomplete({
id: 'autocomplete',
detachedMediaQuery: '',
container,
onStateChange,
});

const searchButton = container.querySelector<HTMLButtonElement>(
'.aa-DetachedSearchButton'
)!;

// Open detached overlay
searchButton.click();

await waitFor(() => {
expect(document.querySelector('.aa-DetachedOverlay')).toBeInTheDocument();
expect(document.body).toHaveClass('aa-Detached');
});

setQuery('a');

const cancelButton = document.querySelector<HTMLButtonElement>(
'.aa-DetachedCancelButton'
)!;

// Close detached overlay
cancelButton.click();

// The detached overlay should close
await waitFor(() => {
expect(
document.querySelector('.aa-DetachedOverlay')
).not.toBeInTheDocument();
expect(document.body).not.toHaveClass('aa-Detached');
});

// The `query` should still be present
expect(onStateChange).toHaveBeenCalledTimes(3);
FabienMotte marked this conversation as resolved.
Show resolved Hide resolved
expect(onStateChange).toHaveBeenLastCalledWith(
expect.objectContaining({
state: expect.objectContaining({ query: 'a' }),
})
);
});

test('reflects the initial `query` in the detached search `button`', async () => {
const container = document.createElement('div');
document.body.appendChild(container);
autocomplete({
id: 'autocomplete',
detachedMediaQuery: '',
container,
initialState: {
query: 'a',
},
});

await waitFor(() => {
expect(
container.querySelector('.aa-DetachedSearchButtonQuery')?.innerHTML
).toEqual('a');
FabienMotte marked this conversation as resolved.
Show resolved Hide resolved
});
});

test('hides detached search `button` placeholder when `query` exists', async () => {
const container = document.createElement('div');
document.body.appendChild(container);
autocomplete({
id: 'autocomplete',
detachedMediaQuery: '',
container,
initialState: {
query: 'a',
},
});

await waitFor(() => {
expect(
container.querySelector('.aa-DetachedSearchButtonPlaceholder')
).toHaveAttribute('hidden');
FabienMotte marked this conversation as resolved.
Show resolved Hide resolved
});
});

test('persists the `query` in the detached search `button` after closing', async () => {
const container = document.createElement('div');
document.body.appendChild(container);
autocomplete({
id: 'autocomplete',
detachedMediaQuery: '',
container,
});

const searchButton = container.querySelector<HTMLButtonElement>(
'.aa-DetachedSearchButton'
)!;

// Open detached overlay
searchButton.click();

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

expect(document.querySelector('.aa-DetachedOverlay')).toBeInTheDocument();
expect(document.body).toHaveClass('aa-Detached');
expect(input).toHaveFocus();

fireEvent.input(input, { target: { value: 'a' } });
});

// Wait for the panel to open
await waitFor(() => {
expect(
document.querySelector<HTMLElement>('.aa-Panel')
).toBeInTheDocument();
});

const cancelButton = document.querySelector<HTMLButtonElement>(
'.aa-DetachedCancelButton'
)!;

// Close detached overlay
cancelButton.click();

// The detached overlay should close
await waitFor(() => {
expect(
document.querySelector('.aa-DetachedOverlay')
).not.toBeInTheDocument();
expect(document.body).not.toHaveClass('aa-Detached');
});

// The detached search button should contain the query
await waitFor(() => {
expect(
container.querySelector('.aa-DetachedSearchButtonQuery')?.innerHTML
).toEqual('a');
FabienMotte marked this conversation as resolved.
Show resolved Hide resolved
expect(
container.querySelector('.aa-DetachedSearchButtonPlaceholder')
).toHaveAttribute('hidden');
FabienMotte marked this conversation as resolved.
Show resolved Hide resolved
});
});
});
2 changes: 0 additions & 2 deletions packages/autocomplete-js/src/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,6 @@ export function autocomplete<TItem extends BaseItem>(
props.value.core.environment.document.body.classList.remove(
'aa-Detached'
);
autocomplete.value.setQuery('');
autocomplete.value.refresh();
}
});
}
Expand Down
22 changes: 17 additions & 5 deletions packages/autocomplete-js/src/createAutocompleteDom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ export function createAutocompleteDom<TItem extends BaseItem>({
...panelProps,
});

const detachedSearchButtonQuery = createDomElement('div', {
class: classNames.detachedSearchButtonQuery,
textContent: state.query,
});
const detachedSearchButtonPlaceholder = createDomElement('div', {
class: classNames.detachedSearchButtonPlaceholder,
hidden: Boolean(state.query),
textContent: placeholder,
});

if (__TEST__) {
setProperties(panel, {
'data-testid': 'panel',
Expand All @@ -148,17 +158,17 @@ export function createAutocompleteDom<TItem extends BaseItem>({
class: classNames.detachedSearchButtonIcon,
children: [SearchIcon({ environment })],
});
const detachedSearchButtonPlaceholder = createDomElement('div', {
class: classNames.detachedSearchButtonPlaceholder,
textContent: placeholder,
});
const detachedSearchButton = createDomElement('button', {
type: 'button',
class: classNames.detachedSearchButton,
onClick() {
setIsModalOpen(true);
},
children: [detachedSearchButtonIcon, detachedSearchButtonPlaceholder],
children: [
detachedSearchButtonIcon,
detachedSearchButtonPlaceholder,
detachedSearchButtonQuery,
],
});
const detachedCancelButton = createDomElement('button', {
type: 'button',
Expand Down Expand Up @@ -188,6 +198,8 @@ export function createAutocompleteDom<TItem extends BaseItem>({
return {
detachedContainer,
detachedOverlay,
detachedSearchButtonQuery,
detachedSearchButtonPlaceholder,
inputWrapper,
input,
root,
Expand Down
1 change: 1 addition & 0 deletions packages/autocomplete-js/src/getDefaultOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const defaultClassNames: AutocompleteClassNames = {
detachedSearchButton: 'aa-DetachedSearchButton',
detachedSearchButtonIcon: 'aa-DetachedSearchButtonIcon',
detachedSearchButtonPlaceholder: 'aa-DetachedSearchButtonPlaceholder',
detachedSearchButtonQuery: 'aa-DetachedSearchButtonQuery',
form: 'aa-Form',
input: 'aa-Input',
inputWrapper: 'aa-InputWrapper',
Expand Down
6 changes: 6 additions & 0 deletions packages/autocomplete-js/src/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ export function renderSearchBox<TItem extends BaseItem>({
setProperties(dom.label, { hidden: state.status === 'stalled' });
setProperties(dom.loadingIndicator, { hidden: state.status !== 'stalled' });
setProperties(dom.clearButton, { hidden: !state.query });
setProperties(dom.detachedSearchButtonQuery, {
textContent: state.query,
});
setProperties(dom.detachedSearchButtonPlaceholder, {
hidden: Boolean(state.query),
});
}

export function renderPanel<TItem extends BaseItem>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type AutocompleteClassNames = {
detachedSearchButton: string;
detachedSearchButtonIcon: string;
detachedSearchButtonPlaceholder: string;
detachedSearchButtonQuery: string;
form: string;
input: string;
inputWrapper: string;
Expand Down
2 changes: 2 additions & 0 deletions packages/autocomplete-js/src/types/AutocompleteDom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ export type AutocompleteDom = {
panel: HTMLDivElement;
detachedContainer: HTMLDivElement;
detachedOverlay: HTMLDivElement;
detachedSearchButtonQuery: HTMLDivElement;
detachedSearchButtonPlaceholder: HTMLDivElement;
};
13 changes: 13 additions & 0 deletions packages/autocomplete-theme-classic/src/theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -930,10 +930,23 @@ body {
color: rgba(var(--aa-primary-color-rgb), 1);
cursor: initial;
display: flex;
flex-shrink: 0;
height: 100%;
justify-content: center;
width: calc(var(--aa-icon-size) + var(--aa-spacing));
}
@at-root .aa-DetachedSearchButtonQuery {
color: rgba(var(--aa-text-color-rgb), 1);
line-height: 1.25em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@at-root .aa-DetachedSearchButtonPlaceholder {
&[hidden] {
display: none;
}
}
}

// Remove scroll on `body`
Expand Down
13 changes: 4 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7554,15 +7554,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"

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:
version "1.0.30001303"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001303.tgz#9b168e4f43ccfc372b86f4bc5a551d9b909c95c9"
integrity sha512-/Mqc1oESndUNszJP0kx0UaQU9kEv9nNtJ7Kn8AdA0mNnH8eR1cj0kG+NbNuC1Wq/b21eA8prhKRA3bbkjONegQ==

caniuse-lite@^1.0.30001161, caniuse-lite@^1.0.30001400:
version "1.0.30001439"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz#ab7371faeb4adff4b74dad1718a6fd122e45d9cb"
integrity sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A==
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:
version "1.0.30001458"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz"
integrity sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w==

capital-case@^1.0.3, capital-case@^1.0.4:
version "1.0.4"
Expand Down