Skip to content

Commit 5a1efc2

Browse files
feat(js): change renderer implementation to virtual DOM (#381)
* feat(js): change renderer implementation to virtual DOM * feat(highlighting): revamp highlighting system to VDOM (#399)
1 parent 3475d12 commit 5a1efc2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+669
-894
lines changed

bundlesize.config.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
},
77
{
88
"path": "packages/autocomplete-js/dist/umd/index.production.js",
9-
"maxSize": "10.2 kB"
9+
"maxSize": "13.5 kB"
1010
},
1111
{
1212
"path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js",
@@ -18,11 +18,11 @@
1818
},
1919
{
2020
"path": "packages/autocomplete-plugin-recent-searches/dist/umd/index.production.js",
21-
"maxSize": "3.25 kB"
21+
"maxSize": "3.5 kB"
2222
},
2323
{
2424
"path": "packages/autocomplete-plugin-query-suggestions/dist/umd/index.production.js",
25-
"maxSize": "2.3 kB"
25+
"maxSize": "2.5 kB"
2626
}
2727
]
2828
}
Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1+
/** @jsx h */
12
import {
23
autocomplete,
34
getAlgoliaHits,
4-
reverseHighlightHit,
5+
highlightHit,
56
} from '@algolia/autocomplete-js';
67
import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights';
78
import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions';
89
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches';
10+
import { Hit } from '@algolia/client-search';
911
import algoliasearch from 'algoliasearch';
12+
import { h } from 'preact';
1013
import insightsClient from 'search-insights';
1114

1215
import '@algolia/autocomplete-theme-classic';
1316

17+
type Product = { name: string; image: string };
18+
type ProductHit = Hit<Product>;
19+
1420
const appId = 'latency';
1521
const apiKey = '6be0576ff61c053d5f9a3225e2a90f76';
1622
const searchClient = algoliasearch(appId, apiKey);
@@ -24,16 +30,18 @@ const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
2430
const querySuggestionsPlugin = createQuerySuggestionsPlugin({
2531
searchClient,
2632
indexName: 'instant_search_demo_query_suggestions',
27-
getSearchParams() {
33+
getSearchParams({ state }) {
2834
return recentSearchesPlugin.data.getAlgoliaSearchParams({
2935
clickAnalytics: true,
36+
hitsPerPage: state.query ? 5 : 10,
3037
});
3138
},
3239
});
3340

3441
autocomplete({
3542
container: '#autocomplete',
3643
placeholder: 'Search',
44+
debug: true,
3745
openOnFocus: true,
3846
plugins: [
3947
algoliaInsightsPlugin,
@@ -48,47 +56,40 @@ autocomplete({
4856
return [
4957
{
5058
getItems() {
51-
return getAlgoliaHits({
59+
return getAlgoliaHits<Product>({
5260
searchClient,
5361
queries: [{ indexName: 'instant_search', query }],
5462
});
5563
},
5664
templates: {
57-
item({ item, root }) {
58-
const itemContent = document.createElement('div');
59-
const ItemSourceIcon = document.createElement('div');
60-
const itemTitle = document.createElement('div');
61-
const sourceIcon = document.createElement('img');
62-
63-
sourceIcon.width = 20;
64-
sourceIcon.height = 20;
65-
sourceIcon.src = item.image;
66-
67-
ItemSourceIcon.classList.add('aa-ItemSourceIcon');
68-
ItemSourceIcon.appendChild(sourceIcon);
69-
70-
itemTitle.innerHTML = reverseHighlightHit({
71-
hit: item,
72-
attribute: 'name',
73-
});
74-
itemTitle.classList.add('aa-ItemTitle');
75-
76-
itemContent.classList.add('aa-ItemContent');
77-
itemContent.appendChild(ItemSourceIcon);
78-
itemContent.appendChild(itemTitle);
79-
80-
root.appendChild(itemContent);
65+
item({ item }) {
66+
return <ProductItem hit={item} />;
8167
},
82-
empty({ root }) {
83-
const itemContent = document.createElement('div');
84-
85-
itemContent.innerHTML = 'No results for this query';
86-
itemContent.classList.add('aa-ItemContent');
87-
88-
root.appendChild(itemContent);
68+
empty() {
69+
return (
70+
<div className="aa-ItemContent">No results for this query.</div>
71+
);
8972
},
9073
},
9174
},
9275
];
9376
},
9477
});
78+
79+
type ProductItemProps = {
80+
hit: ProductHit;
81+
};
82+
83+
function ProductItem({ hit }: ProductItemProps) {
84+
return (
85+
<div className="aa-ItemContent">
86+
<div className="aa-ItemSourceIcon">
87+
<img src={hit.image} alt={hit.name} width="20" height="20" />
88+
</div>
89+
90+
<div className="aa-ItemTitle">
91+
{highlightHit<ProductHit>({ hit, attribute: 'name' })}
92+
</div>
93+
</div>
94+
);
95+
}

examples/js/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@
4747
</div>
4848

4949
<script src="env.ts"></script>
50-
<script src="app.ts"></script>
50+
<script src="app.tsx"></script>
5151
</body>
5252
</html>

examples/js/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"@algolia/autocomplete-plugin-recent-searches": "1.0.0-alpha.38",
1717
"@algolia/autocomplete-preset-algolia": "1.0.0-alpha.38",
1818
"@algolia/autocomplete-theme-classic": "1.0.0-alpha.38",
19+
"@algolia/client-search": "4.8.3",
1920
"algoliasearch": "4.8.3",
21+
"preact": "10.5.7",
2022
"search-insights": "1.6.3"
2123
},
2224
"devDependencies": {

packages/autocomplete-js/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"dependencies": {
3434
"@algolia/autocomplete-core": "1.0.0-alpha.38",
3535
"@algolia/autocomplete-preset-algolia": "1.0.0-alpha.38",
36-
"@algolia/autocomplete-shared": "1.0.0-alpha.38"
36+
"@algolia/autocomplete-shared": "1.0.0-alpha.38",
37+
"preact": "^10.0.0"
3738
},
3839
"devDependencies": {
3940
"@algolia/client-search": "4.8.3"

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

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { fireEvent, waitFor } from '@testing-library/dom';
22

3-
import { wait } from '../../../../test/utils';
43
import { autocomplete } from '../autocomplete';
54

65
describe('autocomplete-js', () => {
@@ -76,6 +75,7 @@ describe('autocomplete-js', () => {
7675
</label>
7776
<div
7877
class="aa-LoadingIndicator"
78+
hidden=""
7979
>
8080
<svg
8181
class="aa-LoadingIcon"
@@ -131,6 +131,7 @@ describe('autocomplete-js', () => {
131131
>
132132
<button
133133
class="aa-ResetButton"
134+
hidden=""
134135
type="reset"
135136
>
136137
<svg
@@ -186,10 +187,7 @@ describe('autocomplete-js', () => {
186187

187188
const input = container.querySelector<HTMLInputElement>('.aa-Input');
188189

189-
fireEvent.input(input, {
190-
target: { value: 'aasdjfaisdf' },
191-
});
192-
input.focus();
190+
fireEvent.input(input, { target: { value: 'a' } });
193191

194192
await waitFor(() => {
195193
expect(
@@ -205,7 +203,7 @@ describe('autocomplete-js', () => {
205203
test('calls renderEmpty without empty template on no results', async () => {
206204
const container = document.createElement('div');
207205
const panelContainer = document.createElement('div');
208-
const renderEmpty = jest.fn(({ root }) => {
206+
const renderEmpty = jest.fn((_params, root) => {
209207
const div = document.createElement('div');
210208
div.innerHTML = 'No results render';
211209

@@ -235,22 +233,24 @@ describe('autocomplete-js', () => {
235233

236234
const input = container.querySelector<HTMLInputElement>('.aa-Input');
237235

238-
fireEvent.input(input, {
239-
target: { value: 'aasdjfaisdf' },
240-
});
241-
input.focus();
236+
fireEvent.input(input, { target: { value: 'a' } });
242237

243238
await waitFor(() => {
244239
expect(
245240
panelContainer.querySelector<HTMLElement>('.aa-Panel')
246241
).toBeInTheDocument();
247242
});
248243

249-
expect(renderEmpty).toHaveBeenCalledWith({
250-
root: expect.anything(),
251-
state: expect.anything(),
252-
sections: expect.anything(),
253-
});
244+
expect(renderEmpty).toHaveBeenCalledWith(
245+
{
246+
state: expect.anything(),
247+
children: expect.anything(),
248+
sections: expect.any(Array),
249+
createElement: expect.anything(),
250+
Fragment: expect.anything(),
251+
},
252+
expect.any(HTMLElement)
253+
);
254254

255255
expect(
256256
panelContainer.querySelector<HTMLElement>('.aa-Panel')
@@ -282,7 +282,7 @@ describe('autocomplete-js', () => {
282282
},
283283
];
284284
},
285-
renderEmpty({ root }) {
285+
renderEmpty(_params, root) {
286286
const div = document.createElement('div');
287287
div.innerHTML = 'No results render';
288288

@@ -292,10 +292,7 @@ describe('autocomplete-js', () => {
292292

293293
const input = container.querySelector<HTMLInputElement>('.aa-Input');
294294

295-
fireEvent.input(input, {
296-
target: { value: 'aasdjfaisdf' },
297-
});
298-
input.focus();
295+
fireEvent.input(input, { target: { value: 'a' } });
299296

300297
await waitFor(() => {
301298
expect(
@@ -308,7 +305,7 @@ describe('autocomplete-js', () => {
308305
).toHaveTextContent('No results template');
309306
});
310307

311-
test('allows user-provided shouldPanelShow', async () => {
308+
test('allows user-provided shouldPanelShow', () => {
312309
const container = document.createElement('div');
313310
const panelContainer = document.createElement('div');
314311

@@ -339,12 +336,7 @@ describe('autocomplete-js', () => {
339336

340337
const input = container.querySelector<HTMLInputElement>('.aa-Input');
341338

342-
fireEvent.input(input, {
343-
target: { value: 'aasdjfaisdf' },
344-
});
345-
input.focus();
346-
347-
await wait(50);
339+
fireEvent.input(input, { target: { value: 'a' } });
348340

349341
expect(
350342
panelContainer.querySelector<HTMLElement>('.aa-Panel')
@@ -473,10 +465,7 @@ describe('autocomplete-js', () => {
473465

474466
const input = container.querySelector<HTMLInputElement>('.aa-Input');
475467

476-
fireEvent.input(input, {
477-
target: { value: 'a' },
478-
});
479-
input.focus();
468+
fireEvent.input(input, { target: { value: 'a' } });
480469

481470
expect(input).toHaveValue('a');
482471
});

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,23 @@ describe('reverseHighlightHit', () => {
1919
},
2020
attribute: 'query',
2121
})
22-
).toMatchInlineSnapshot(`"<mark>amazon </mark>fire tablet<mark>s</mark>"`);
22+
).toEqual([
23+
expect.objectContaining({
24+
type: 'mark',
25+
props: {
26+
children: 'amazon ',
27+
},
28+
}),
29+
'fire',
30+
' ',
31+
'tablet',
32+
expect.objectContaining({
33+
type: 'mark',
34+
props: {
35+
children: 's',
36+
},
37+
}),
38+
]);
2339
});
2440

2541
test('returns a reversed fully highlighted hit', () => {
@@ -40,7 +56,7 @@ describe('reverseHighlightHit', () => {
4056
},
4157
attribute: 'query',
4258
})
43-
).toMatchInlineSnapshot(`"amazon fire tablets"`);
59+
).toEqual(['amazon', ' ', 'fire', ' ', 'tablets']);
4460
});
4561

4662
test('returns a reversed empty highlighted query hit', () => {
@@ -60,6 +76,6 @@ describe('reverseHighlightHit', () => {
6076
},
6177
attribute: 'query',
6278
})
63-
).toMatchInlineSnapshot(`"amazon fire tablets"`);
79+
).toEqual(['amazon fire tablets']);
6480
});
6581
});

0 commit comments

Comments
 (0)