@constructor-io/constructorio-ui-autocomplete 1.21.0 → 1.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constructorio-ui-autocomplete-bundled.js +10 -10
- package/lib/cjs/beaconUtils.js +171 -0
- package/lib/cjs/constants.js +5 -0
- package/lib/cjs/hooks/useCioAutocomplete.js +2 -1
- package/lib/cjs/hooks/useDebouncedFetchSections.js +8 -2
- package/lib/cjs/hooks/useSections.js +2 -1
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/beaconUtils.js +157 -0
- package/lib/mjs/constants.js +5 -0
- package/lib/mjs/hooks/useCioAutocomplete.js +2 -1
- package/lib/mjs/hooks/useDebouncedFetchSections.js +8 -2
- package/lib/mjs/hooks/useSections.js +2 -1
- package/lib/mjs/version.js +1 -1
- package/lib/types/beaconUtils.d.ts +27 -0
- package/lib/types/components/Autocomplete/CioAutocompleteProvider.d.ts +1 -0
- package/lib/types/constants.d.ts +1 -1
- package/lib/types/hooks/useCioAutocomplete.d.ts +1 -0
- package/lib/types/hooks/useSections.d.ts +1 -0
- package/lib/types/types.d.ts +1 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.storeRecentAction = exports.getLastAction = exports.getRecentActions = exports.storeRecentSearch = exports.getRecentSearches = exports.storageGetArray = exports.storageGetItem = exports.cleanTerm = exports.storageSetItem = exports.getStorageEngine = exports.CONSTANTS = void 0;
|
|
4
|
+
// eslint-disable-next-line import/no-cycle
|
|
5
|
+
const utils_1 = require("./utils");
|
|
6
|
+
exports.CONSTANTS = {
|
|
7
|
+
SEARCH_SUBMIT: 'SEARCH_SUBMIT',
|
|
8
|
+
RECENT_ACTIONS_STORAGE_KEY: {
|
|
9
|
+
scope: 'session',
|
|
10
|
+
key: '_constructorio_recent_actions',
|
|
11
|
+
},
|
|
12
|
+
RECENT_SEARCHES_STORAGE_KEY: {
|
|
13
|
+
scope: 'local',
|
|
14
|
+
key: '_constructorio_recent_searches',
|
|
15
|
+
},
|
|
16
|
+
SEARCH_TERM_STORAGE_KEY: {
|
|
17
|
+
scope: 'session',
|
|
18
|
+
key: '_constructorio_search_term',
|
|
19
|
+
},
|
|
20
|
+
RECENT_SEARCHES_STORAGE_COUNT: 100,
|
|
21
|
+
RECENT_ACTION_STORAGE_COUNT: 5,
|
|
22
|
+
};
|
|
23
|
+
// Return storage engine based on scope
|
|
24
|
+
const getStorageEngine = (scope) => {
|
|
25
|
+
if (scope === 'local') {
|
|
26
|
+
return localStorage;
|
|
27
|
+
}
|
|
28
|
+
return sessionStorage;
|
|
29
|
+
};
|
|
30
|
+
exports.getStorageEngine = getStorageEngine;
|
|
31
|
+
// Set item in storage
|
|
32
|
+
const storageSetItem = (key, value) => {
|
|
33
|
+
try {
|
|
34
|
+
const storageEngine = (0, exports.getStorageEngine)(key.scope);
|
|
35
|
+
return storageEngine.setItem(key.key, value);
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
(0, utils_1.logger)(`storageSetItem error: ${e}`);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
exports.storageSetItem = storageSetItem;
|
|
43
|
+
/*
|
|
44
|
+
* Use the text content of the resulting HTML node(s) created from the
|
|
45
|
+
* term
|
|
46
|
+
*/
|
|
47
|
+
const cleanTerm = (term) => {
|
|
48
|
+
// Remove all scripts and image tags from term
|
|
49
|
+
const tmp = document.implementation.createHTMLDocument('');
|
|
50
|
+
tmp.body.innerHTML = term;
|
|
51
|
+
const scriptsAndImages = [...tmp.body.querySelectorAll('script, img')];
|
|
52
|
+
scriptsAndImages.forEach((el) => {
|
|
53
|
+
tmp.body.removeChild(el);
|
|
54
|
+
});
|
|
55
|
+
const nodes = tmp.body.childNodes;
|
|
56
|
+
const texts = [...nodes].map((node) => node.innerText || node.textContent);
|
|
57
|
+
return texts.join('');
|
|
58
|
+
};
|
|
59
|
+
exports.cleanTerm = cleanTerm;
|
|
60
|
+
// Retrieves item from storage for key
|
|
61
|
+
const storageGetItem = (key) => {
|
|
62
|
+
try {
|
|
63
|
+
const storageEngine = (0, exports.getStorageEngine)(key.scope);
|
|
64
|
+
return storageEngine.getItem(key.key);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
(0, utils_1.logger)(`storageGetItem error: ${e}`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
exports.storageGetItem = storageGetItem;
|
|
72
|
+
// Retrieve array from storage
|
|
73
|
+
const storageGetArray = (key) => {
|
|
74
|
+
const item = (0, exports.storageGetItem)(key);
|
|
75
|
+
if (item) {
|
|
76
|
+
try {
|
|
77
|
+
const array = JSON.parse(item);
|
|
78
|
+
return array;
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
(0, utils_1.logger)(`storageGetArray error: ${e}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
};
|
|
86
|
+
exports.storageGetArray = storageGetArray;
|
|
87
|
+
/*
|
|
88
|
+
* Returns a list of recent searches
|
|
89
|
+
*/
|
|
90
|
+
const getRecentSearches = () => {
|
|
91
|
+
const recentSearches = (0, exports.storageGetArray)(exports.CONSTANTS.RECENT_SEARCHES_STORAGE_KEY) || [];
|
|
92
|
+
// upgrade the array to store timestamps if it isn't already
|
|
93
|
+
recentSearches.forEach((item, i) => {
|
|
94
|
+
if (typeof item === 'string') {
|
|
95
|
+
recentSearches[i] = {
|
|
96
|
+
term: item,
|
|
97
|
+
ts: Date.now(),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
return recentSearches;
|
|
102
|
+
};
|
|
103
|
+
exports.getRecentSearches = getRecentSearches;
|
|
104
|
+
/*
|
|
105
|
+
* Stores a recent search
|
|
106
|
+
*/
|
|
107
|
+
const storeRecentSearch = (term, suggestionData) => {
|
|
108
|
+
const cleanedTerm = (0, exports.cleanTerm)(term.trim());
|
|
109
|
+
let recentSearches = (0, exports.getRecentSearches)();
|
|
110
|
+
if (cleanedTerm.length > 0) {
|
|
111
|
+
// this ensures it goes onto the end of the array, and only there
|
|
112
|
+
recentSearches = recentSearches.filter((item) => { var _a; return ((_a = item.term) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== cleanedTerm.toUpperCase(); });
|
|
113
|
+
// reset the section to the original section if it's present because that's
|
|
114
|
+
// the real section we want to store
|
|
115
|
+
if (suggestionData === null || suggestionData === void 0 ? void 0 : suggestionData.original_section) {
|
|
116
|
+
/* eslint-disable no-param-reassign */
|
|
117
|
+
suggestionData.section = suggestionData.original_section;
|
|
118
|
+
delete suggestionData.original_section;
|
|
119
|
+
delete suggestionData.is_meta_section;
|
|
120
|
+
/* eslint-enable no-param-reassign */
|
|
121
|
+
}
|
|
122
|
+
recentSearches.push({
|
|
123
|
+
term: cleanedTerm,
|
|
124
|
+
ts: Date.now(),
|
|
125
|
+
data: suggestionData,
|
|
126
|
+
});
|
|
127
|
+
while (recentSearches.length > exports.CONSTANTS.RECENT_SEARCHES_STORAGE_COUNT) {
|
|
128
|
+
recentSearches.shift();
|
|
129
|
+
}
|
|
130
|
+
(0, exports.storageSetItem)(exports.CONSTANTS.RECENT_SEARCHES_STORAGE_KEY, JSON.stringify(recentSearches));
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
exports.storeRecentSearch = storeRecentSearch;
|
|
134
|
+
/*
|
|
135
|
+
* Returns a list of recent actions
|
|
136
|
+
*/
|
|
137
|
+
const getRecentActions = () => (0, exports.storageGetArray)(exports.CONSTANTS.RECENT_ACTIONS_STORAGE_KEY) || [];
|
|
138
|
+
exports.getRecentActions = getRecentActions;
|
|
139
|
+
/*
|
|
140
|
+
* Returns the last action
|
|
141
|
+
*/
|
|
142
|
+
const getLastAction = () => {
|
|
143
|
+
var _a;
|
|
144
|
+
const recentActions = (0, exports.getRecentActions)();
|
|
145
|
+
if (recentActions && recentActions.length) {
|
|
146
|
+
return (_a = recentActions[recentActions.length - 1]) === null || _a === void 0 ? void 0 : _a.action;
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
};
|
|
150
|
+
exports.getLastAction = getLastAction;
|
|
151
|
+
/*
|
|
152
|
+
* Stores a recent action
|
|
153
|
+
* Currently only storing search submits, search loads and browse loads
|
|
154
|
+
*/
|
|
155
|
+
const storeRecentAction = (action) => {
|
|
156
|
+
let recentActions = (0, exports.getRecentActions)();
|
|
157
|
+
if (action) {
|
|
158
|
+
// Only add non-consecutive actions
|
|
159
|
+
if ((0, exports.getLastAction)() !== action) {
|
|
160
|
+
recentActions.push({
|
|
161
|
+
action,
|
|
162
|
+
ts: Date.now(),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (recentActions.length > exports.CONSTANTS.RECENT_ACTION_STORAGE_COUNT) {
|
|
166
|
+
recentActions = recentActions.slice(-1 * exports.CONSTANTS.RECENT_ACTION_STORAGE_COUNT);
|
|
167
|
+
}
|
|
168
|
+
(0, exports.storageSetItem)(exports.CONSTANTS.RECENT_ACTIONS_STORAGE_KEY, JSON.stringify(recentActions));
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
exports.storeRecentAction = storeRecentAction;
|
package/lib/cjs/constants.js
CHANGED
|
@@ -34,6 +34,11 @@ const {
|
|
|
34
34
|
// must be used for a hooks integrations
|
|
35
35
|
query: string, // current input field value
|
|
36
36
|
sections: [{...}], // array of sections data to render in menu list
|
|
37
|
+
totalNumResultsPerSection: {
|
|
38
|
+
"Products": number,
|
|
39
|
+
"Search Suggestions": number,
|
|
40
|
+
...
|
|
41
|
+
}, // total number of product and search suggestion results (and other sections)
|
|
37
42
|
getFormProps: () => ({...})), // prop getter for jsx form element
|
|
38
43
|
getInputProps: () => ({...})), // prop getter for jsx input element
|
|
39
44
|
getMenuProps: () => ({...})), // prop getter for jsx element rendering the results container
|
|
@@ -57,7 +57,7 @@ const useCioAutocomplete = (options) => {
|
|
|
57
57
|
const previousQuery = (0, usePrevious_1.default)(query);
|
|
58
58
|
const cioClient = (0, useCioClient_1.default)({ apiKey, cioJsClient, cioJsClientOptions });
|
|
59
59
|
// Get autocomplete sections (autocomplete + recommendations + custom)
|
|
60
|
-
const { activeSections, activeSectionsWithData, zeroStateActiveSections, request } = (0, useSections_1.default)(query, cioClient, sections, zeroStateSections, advancedParameters);
|
|
60
|
+
const { activeSections, activeSectionsWithData, zeroStateActiveSections, request, totalNumResultsPerSection, } = (0, useSections_1.default)(query, cioClient, sections, zeroStateSections, advancedParameters);
|
|
61
61
|
const features = (0, react_1.useMemo)(() => (0, utils_1.getFeatures)(request), [request]);
|
|
62
62
|
// Get dropdown items array from active sections (autocomplete + recommendations + custom)
|
|
63
63
|
const items = (0, react_1.useMemo)(() => (0, utils_1.getItemsForActiveSections)(activeSectionsWithData), [activeSectionsWithData]);
|
|
@@ -69,6 +69,7 @@ const useCioAutocomplete = (options) => {
|
|
|
69
69
|
return {
|
|
70
70
|
query,
|
|
71
71
|
sections: activeSectionsWithData,
|
|
72
|
+
totalNumResultsPerSection,
|
|
72
73
|
request,
|
|
73
74
|
featureToggles: features,
|
|
74
75
|
isOpen: isOpen && (items === null || items === void 0 ? void 0 : items.length) > 0,
|
|
@@ -28,12 +28,18 @@ const transformResponse = (response, options) => {
|
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
});
|
|
31
|
-
return {
|
|
31
|
+
return {
|
|
32
|
+
sectionsData: newSectionsData,
|
|
33
|
+
request: response === null || response === void 0 ? void 0 : response.request,
|
|
34
|
+
totalNumResultsPerSection: response === null || response === void 0 ? void 0 : response.total_num_results_per_section,
|
|
35
|
+
};
|
|
32
36
|
};
|
|
33
37
|
const useDebouncedFetchSection = (query, cioClient, autocompleteSections, advancedParameters) => {
|
|
38
|
+
// This says sectionsData but it also contain request information
|
|
34
39
|
const [sectionsData, setSectionsData] = (0, react_1.useState)({
|
|
35
40
|
sectionsData: {},
|
|
36
41
|
request: {},
|
|
42
|
+
totalNumResultsPerSection: {},
|
|
37
43
|
});
|
|
38
44
|
const debouncedSearchTerm = (0, useDebounce_1.default)(query, advancedParameters === null || advancedParameters === void 0 ? void 0 : advancedParameters.debounce);
|
|
39
45
|
const { numTermsWithGroupSuggestions = 0, numGroupsSuggestedPerTerm = 0 } = advancedParameters || {};
|
|
@@ -65,7 +71,7 @@ const useDebouncedFetchSection = (query, cioClient, autocompleteSections, advanc
|
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
73
|
else if (!debouncedSearchTerm) {
|
|
68
|
-
setSectionsData({ sectionsData: {}, request: {} });
|
|
74
|
+
setSectionsData({ sectionsData: {}, request: {}, totalNumResultsPerSection: {} });
|
|
69
75
|
}
|
|
70
76
|
}))();
|
|
71
77
|
}, [
|
|
@@ -16,7 +16,7 @@ function useSections(query, cioClient, sections, zeroStateSections, advancedPara
|
|
|
16
16
|
const autocompleteSections = (0, react_1.useMemo)(() => activeSections === null || activeSections === void 0 ? void 0 : activeSections.filter((config) => (0, typeGuards_1.isAutocompleteSection)(config)), [activeSections]);
|
|
17
17
|
const recommendationsSections = (0, react_1.useMemo)(() => activeSections === null || activeSections === void 0 ? void 0 : activeSections.filter((config) => (0, typeGuards_1.isRecommendationsSection)(config)), [activeSections]);
|
|
18
18
|
// Fetch Autocomplete Results
|
|
19
|
-
const { sectionsData: autocompleteResults, request } = (0, useDebouncedFetchSections_1.default)(query, cioClient, autocompleteSections, advancedParameters);
|
|
19
|
+
const { sectionsData: autocompleteResults, request, totalNumResultsPerSection, } = (0, useDebouncedFetchSections_1.default)(query, cioClient, autocompleteSections, advancedParameters);
|
|
20
20
|
// Fetch Recommendations Results
|
|
21
21
|
const { recommendationsResults, podsData } = (0, useFetchRecommendationPod_1.default)(cioClient, recommendationsSections);
|
|
22
22
|
// Remove sections if necessary
|
|
@@ -56,6 +56,7 @@ function useSections(query, cioClient, sections, zeroStateSections, advancedPara
|
|
|
56
56
|
activeSectionsWithData,
|
|
57
57
|
zeroStateActiveSections,
|
|
58
58
|
request,
|
|
59
|
+
totalNumResultsPerSection,
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
62
|
exports.default = useSections;
|
package/lib/cjs/version.js
CHANGED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// eslint-disable-next-line import/no-cycle
|
|
2
|
+
import { logger } from './utils';
|
|
3
|
+
export const CONSTANTS = {
|
|
4
|
+
SEARCH_SUBMIT: 'SEARCH_SUBMIT',
|
|
5
|
+
RECENT_ACTIONS_STORAGE_KEY: {
|
|
6
|
+
scope: 'session',
|
|
7
|
+
key: '_constructorio_recent_actions',
|
|
8
|
+
},
|
|
9
|
+
RECENT_SEARCHES_STORAGE_KEY: {
|
|
10
|
+
scope: 'local',
|
|
11
|
+
key: '_constructorio_recent_searches',
|
|
12
|
+
},
|
|
13
|
+
SEARCH_TERM_STORAGE_KEY: {
|
|
14
|
+
scope: 'session',
|
|
15
|
+
key: '_constructorio_search_term',
|
|
16
|
+
},
|
|
17
|
+
RECENT_SEARCHES_STORAGE_COUNT: 100,
|
|
18
|
+
RECENT_ACTION_STORAGE_COUNT: 5,
|
|
19
|
+
};
|
|
20
|
+
// Return storage engine based on scope
|
|
21
|
+
export const getStorageEngine = (scope) => {
|
|
22
|
+
if (scope === 'local') {
|
|
23
|
+
return localStorage;
|
|
24
|
+
}
|
|
25
|
+
return sessionStorage;
|
|
26
|
+
};
|
|
27
|
+
// Set item in storage
|
|
28
|
+
export const storageSetItem = (key, value) => {
|
|
29
|
+
try {
|
|
30
|
+
const storageEngine = getStorageEngine(key.scope);
|
|
31
|
+
return storageEngine.setItem(key.key, value);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
logger(`storageSetItem error: ${e}`);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/*
|
|
39
|
+
* Use the text content of the resulting HTML node(s) created from the
|
|
40
|
+
* term
|
|
41
|
+
*/
|
|
42
|
+
export const cleanTerm = (term) => {
|
|
43
|
+
// Remove all scripts and image tags from term
|
|
44
|
+
const tmp = document.implementation.createHTMLDocument('');
|
|
45
|
+
tmp.body.innerHTML = term;
|
|
46
|
+
const scriptsAndImages = [...tmp.body.querySelectorAll('script, img')];
|
|
47
|
+
scriptsAndImages.forEach((el) => {
|
|
48
|
+
tmp.body.removeChild(el);
|
|
49
|
+
});
|
|
50
|
+
const nodes = tmp.body.childNodes;
|
|
51
|
+
const texts = [...nodes].map((node) => node.innerText || node.textContent);
|
|
52
|
+
return texts.join('');
|
|
53
|
+
};
|
|
54
|
+
// Retrieves item from storage for key
|
|
55
|
+
export const storageGetItem = (key) => {
|
|
56
|
+
try {
|
|
57
|
+
const storageEngine = getStorageEngine(key.scope);
|
|
58
|
+
return storageEngine.getItem(key.key);
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
logger(`storageGetItem error: ${e}`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
// Retrieve array from storage
|
|
66
|
+
export const storageGetArray = (key) => {
|
|
67
|
+
const item = storageGetItem(key);
|
|
68
|
+
if (item) {
|
|
69
|
+
try {
|
|
70
|
+
const array = JSON.parse(item);
|
|
71
|
+
return array;
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
logger(`storageGetArray error: ${e}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
79
|
+
/*
|
|
80
|
+
* Returns a list of recent searches
|
|
81
|
+
*/
|
|
82
|
+
export const getRecentSearches = () => {
|
|
83
|
+
const recentSearches = storageGetArray(CONSTANTS.RECENT_SEARCHES_STORAGE_KEY) || [];
|
|
84
|
+
// upgrade the array to store timestamps if it isn't already
|
|
85
|
+
recentSearches.forEach((item, i) => {
|
|
86
|
+
if (typeof item === 'string') {
|
|
87
|
+
recentSearches[i] = {
|
|
88
|
+
term: item,
|
|
89
|
+
ts: Date.now(),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return recentSearches;
|
|
94
|
+
};
|
|
95
|
+
/*
|
|
96
|
+
* Stores a recent search
|
|
97
|
+
*/
|
|
98
|
+
export const storeRecentSearch = (term, suggestionData) => {
|
|
99
|
+
const cleanedTerm = cleanTerm(term.trim());
|
|
100
|
+
let recentSearches = getRecentSearches();
|
|
101
|
+
if (cleanedTerm.length > 0) {
|
|
102
|
+
// this ensures it goes onto the end of the array, and only there
|
|
103
|
+
recentSearches = recentSearches.filter((item) => item.term?.toUpperCase() !== cleanedTerm.toUpperCase());
|
|
104
|
+
// reset the section to the original section if it's present because that's
|
|
105
|
+
// the real section we want to store
|
|
106
|
+
if (suggestionData?.original_section) {
|
|
107
|
+
/* eslint-disable no-param-reassign */
|
|
108
|
+
suggestionData.section = suggestionData.original_section;
|
|
109
|
+
delete suggestionData.original_section;
|
|
110
|
+
delete suggestionData.is_meta_section;
|
|
111
|
+
/* eslint-enable no-param-reassign */
|
|
112
|
+
}
|
|
113
|
+
recentSearches.push({
|
|
114
|
+
term: cleanedTerm,
|
|
115
|
+
ts: Date.now(),
|
|
116
|
+
data: suggestionData,
|
|
117
|
+
});
|
|
118
|
+
while (recentSearches.length > CONSTANTS.RECENT_SEARCHES_STORAGE_COUNT) {
|
|
119
|
+
recentSearches.shift();
|
|
120
|
+
}
|
|
121
|
+
storageSetItem(CONSTANTS.RECENT_SEARCHES_STORAGE_KEY, JSON.stringify(recentSearches));
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
/*
|
|
125
|
+
* Returns a list of recent actions
|
|
126
|
+
*/
|
|
127
|
+
export const getRecentActions = () => storageGetArray(CONSTANTS.RECENT_ACTIONS_STORAGE_KEY) || [];
|
|
128
|
+
/*
|
|
129
|
+
* Returns the last action
|
|
130
|
+
*/
|
|
131
|
+
export const getLastAction = () => {
|
|
132
|
+
const recentActions = getRecentActions();
|
|
133
|
+
if (recentActions && recentActions.length) {
|
|
134
|
+
return recentActions[recentActions.length - 1]?.action;
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
};
|
|
138
|
+
/*
|
|
139
|
+
* Stores a recent action
|
|
140
|
+
* Currently only storing search submits, search loads and browse loads
|
|
141
|
+
*/
|
|
142
|
+
export const storeRecentAction = (action) => {
|
|
143
|
+
let recentActions = getRecentActions();
|
|
144
|
+
if (action) {
|
|
145
|
+
// Only add non-consecutive actions
|
|
146
|
+
if (getLastAction() !== action) {
|
|
147
|
+
recentActions.push({
|
|
148
|
+
action,
|
|
149
|
+
ts: Date.now(),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (recentActions.length > CONSTANTS.RECENT_ACTION_STORAGE_COUNT) {
|
|
153
|
+
recentActions = recentActions.slice(-1 * CONSTANTS.RECENT_ACTION_STORAGE_COUNT);
|
|
154
|
+
}
|
|
155
|
+
storageSetItem(CONSTANTS.RECENT_ACTIONS_STORAGE_KEY, JSON.stringify(recentActions));
|
|
156
|
+
}
|
|
157
|
+
};
|
package/lib/mjs/constants.js
CHANGED
|
@@ -31,6 +31,11 @@ const {
|
|
|
31
31
|
// must be used for a hooks integrations
|
|
32
32
|
query: string, // current input field value
|
|
33
33
|
sections: [{...}], // array of sections data to render in menu list
|
|
34
|
+
totalNumResultsPerSection: {
|
|
35
|
+
"Products": number,
|
|
36
|
+
"Search Suggestions": number,
|
|
37
|
+
...
|
|
38
|
+
}, // total number of product and search suggestion results (and other sections)
|
|
34
39
|
getFormProps: () => ({...})), // prop getter for jsx form element
|
|
35
40
|
getInputProps: () => ({...})), // prop getter for jsx input element
|
|
36
41
|
getMenuProps: () => ({...})), // prop getter for jsx element rendering the results container
|
|
@@ -53,7 +53,7 @@ const useCioAutocomplete = (options) => {
|
|
|
53
53
|
const previousQuery = usePrevious(query);
|
|
54
54
|
const cioClient = useCioClient({ apiKey, cioJsClient, cioJsClientOptions });
|
|
55
55
|
// Get autocomplete sections (autocomplete + recommendations + custom)
|
|
56
|
-
const { activeSections, activeSectionsWithData, zeroStateActiveSections, request } = useSections(query, cioClient, sections, zeroStateSections, advancedParameters);
|
|
56
|
+
const { activeSections, activeSectionsWithData, zeroStateActiveSections, request, totalNumResultsPerSection, } = useSections(query, cioClient, sections, zeroStateSections, advancedParameters);
|
|
57
57
|
const features = useMemo(() => getFeatures(request), [request]);
|
|
58
58
|
// Get dropdown items array from active sections (autocomplete + recommendations + custom)
|
|
59
59
|
const items = useMemo(() => getItemsForActiveSections(activeSectionsWithData), [activeSectionsWithData]);
|
|
@@ -65,6 +65,7 @@ const useCioAutocomplete = (options) => {
|
|
|
65
65
|
return {
|
|
66
66
|
query,
|
|
67
67
|
sections: activeSectionsWithData,
|
|
68
|
+
totalNumResultsPerSection,
|
|
68
69
|
request,
|
|
69
70
|
featureToggles: features,
|
|
70
71
|
isOpen: isOpen && items?.length > 0,
|
|
@@ -29,12 +29,18 @@ const transformResponse = (response, options) => {
|
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
|
-
return {
|
|
32
|
+
return {
|
|
33
|
+
sectionsData: newSectionsData,
|
|
34
|
+
request: response?.request,
|
|
35
|
+
totalNumResultsPerSection: response?.total_num_results_per_section,
|
|
36
|
+
};
|
|
33
37
|
};
|
|
34
38
|
const useDebouncedFetchSection = (query, cioClient, autocompleteSections, advancedParameters) => {
|
|
39
|
+
// This says sectionsData but it also contain request information
|
|
35
40
|
const [sectionsData, setSectionsData] = useState({
|
|
36
41
|
sectionsData: {},
|
|
37
42
|
request: {},
|
|
43
|
+
totalNumResultsPerSection: {},
|
|
38
44
|
});
|
|
39
45
|
const debouncedSearchTerm = useDebounce(query, advancedParameters?.debounce);
|
|
40
46
|
const { numTermsWithGroupSuggestions = 0, numGroupsSuggestedPerTerm = 0 } = advancedParameters || {};
|
|
@@ -71,7 +77,7 @@ const useDebouncedFetchSection = (query, cioClient, autocompleteSections, advanc
|
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
else if (!debouncedSearchTerm) {
|
|
74
|
-
setSectionsData({ sectionsData: {}, request: {} });
|
|
80
|
+
setSectionsData({ sectionsData: {}, request: {}, totalNumResultsPerSection: {} });
|
|
75
81
|
}
|
|
76
82
|
})();
|
|
77
83
|
}, [
|
|
@@ -13,7 +13,7 @@ export default function useSections(query, cioClient, sections, zeroStateSection
|
|
|
13
13
|
const autocompleteSections = useMemo(() => activeSections?.filter((config) => isAutocompleteSection(config)), [activeSections]);
|
|
14
14
|
const recommendationsSections = useMemo(() => activeSections?.filter((config) => isRecommendationsSection(config)), [activeSections]);
|
|
15
15
|
// Fetch Autocomplete Results
|
|
16
|
-
const { sectionsData: autocompleteResults, request } = useDebouncedFetchSection(query, cioClient, autocompleteSections, advancedParameters);
|
|
16
|
+
const { sectionsData: autocompleteResults, request, totalNumResultsPerSection, } = useDebouncedFetchSection(query, cioClient, autocompleteSections, advancedParameters);
|
|
17
17
|
// Fetch Recommendations Results
|
|
18
18
|
const { recommendationsResults, podsData } = useFetchRecommendationPod(cioClient, recommendationsSections);
|
|
19
19
|
// Remove sections if necessary
|
|
@@ -52,5 +52,6 @@ export default function useSections(query, cioClient, sections, zeroStateSection
|
|
|
52
52
|
activeSectionsWithData,
|
|
53
53
|
zeroStateActiveSections,
|
|
54
54
|
request,
|
|
55
|
+
totalNumResultsPerSection,
|
|
55
56
|
};
|
|
56
57
|
}
|
package/lib/mjs/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '1.
|
|
1
|
+
export default '1.22.1';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const CONSTANTS: {
|
|
2
|
+
SEARCH_SUBMIT: string;
|
|
3
|
+
RECENT_ACTIONS_STORAGE_KEY: {
|
|
4
|
+
scope: string;
|
|
5
|
+
key: string;
|
|
6
|
+
};
|
|
7
|
+
RECENT_SEARCHES_STORAGE_KEY: {
|
|
8
|
+
scope: string;
|
|
9
|
+
key: string;
|
|
10
|
+
};
|
|
11
|
+
SEARCH_TERM_STORAGE_KEY: {
|
|
12
|
+
scope: string;
|
|
13
|
+
key: string;
|
|
14
|
+
};
|
|
15
|
+
RECENT_SEARCHES_STORAGE_COUNT: number;
|
|
16
|
+
RECENT_ACTION_STORAGE_COUNT: number;
|
|
17
|
+
};
|
|
18
|
+
export declare const getStorageEngine: (scope: any) => Storage;
|
|
19
|
+
export declare const storageSetItem: (key: any, value: any) => void | null;
|
|
20
|
+
export declare const cleanTerm: (term: any) => string;
|
|
21
|
+
export declare const storageGetItem: (key: any) => string | null;
|
|
22
|
+
export declare const storageGetArray: (key: any) => any;
|
|
23
|
+
export declare const getRecentSearches: () => any;
|
|
24
|
+
export declare const storeRecentSearch: (term: any, suggestionData: any) => void;
|
|
25
|
+
export declare const getRecentActions: () => any;
|
|
26
|
+
export declare const getLastAction: () => any;
|
|
27
|
+
export declare const storeRecentAction: (action: any) => void;
|
|
@@ -3,6 +3,7 @@ import { CioAutocompleteProps } from '../../types';
|
|
|
3
3
|
export declare const CioAutocompleteContext: React.Context<{
|
|
4
4
|
query: string;
|
|
5
5
|
sections: import("../../types").Section[];
|
|
6
|
+
totalNumResultsPerSection: Record<string, number>;
|
|
6
7
|
request: Partial<import("@constructor-io/constructorio-client-javascript/lib/types").AutocompleteRequestType>;
|
|
7
8
|
featureToggles: {
|
|
8
9
|
featureDisplaySearchSuggestionImages: boolean;
|
package/lib/types/constants.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AutocompleteSubmitEvent } from './types';
|
|
2
2
|
export declare const apiKey = "key_M57QS8SMPdLdLx4x";
|
|
3
3
|
export declare const componentDescription = "- import `CioAutocomplete` to render in your JSX.\n- This component handles state management, data fetching, and rendering logic.\n- To use this component, an `apiKey` or `cioJsClient` are required, and an `onSubmit` callback must be passed. All other values are optional.\n- Use different props to configure the behavior of this component.\n- The following stories shows how different props affect the component's behavior\n\n> Note: when we say `cioJsClient`, we are referring to an instance of the [constructorio-client-javascript](https://www.npmjs.com/package/@constructor-io/constructorio-client-javascript)\n";
|
|
4
|
-
export declare const hookDescription = "- import `useCioAutocomplete` and call this custom hook in a functional component.\n- This hook leaves rendering logic up to you, while handling:\n - state management\n - data fetching\n - keyboard navigation\n - mouse interactions\n - focus and submit event handling\n - accessibility\n- To use this hook, an `apiKey` or `cioJsClient` are required, and an `onSubmit` callback must be passed to the `useCioAutocomplete` hook to configure behavior. All other values are optional.\n- use the <a href=\"https://kentcdodds.com/blog/how-to-give-rendering-control-to-users-with-prop-getters\" target=\"__blank\">prop getters</a> and other variables returned by this hook (below) to leverage the functionality described above with jsx elements in your react component definitions\n\n> **Important: Please note that all of the select or submit events should go through the `onSubmit` callback. The `getItemProps` hook returns click and keyboard handlers which should be applied to the elements using the spread operator (see code examples below). Adding an `onClick` event manually to the elements would override the default behavior of the library preventing it from sending tracking events and would cause different behavior between click and keyboard events. Selecting an item or submitting the search would both cause `onSubmit` to be called. **\n\nCalling the `useCioAutocomplete` hook returns an object with the following keys:\n\n```jsx\nconst {\n // must be used for a hooks integrations\n query: string, // current input field value\n sections: [{...}], // array of sections data to render in menu list\n getFormProps: () => ({...})), // prop getter for jsx form element\n getInputProps: () => ({...})), // prop getter for jsx input element\n getMenuProps: () => ({...})), // prop getter for jsx element rendering the results container\n getItemProps: (item) => ({...})), // prop getter for jsx element rendering each result\n getSectionProps: (section: Section) => ({...})), // prop getter for jsx element rendering each section.\n\n // available for use, but not required for all use cases\n selectedItem: item, // undefined or current selected item (via hover or arrow keys)\n isOpen: boolean, // current state of the menu list\n openMenu: () => void, // open menu\n closeMenu: () => void, // close menu\n setQuery: () => void, // update the current input field value\n getLabelProps: () => ({...})), // prop getter for a jsx label element\n cioJsClient, // instance of constructorio-client-javascript\n } = useCioAutocomplete(args);\n```\n\n> Note: when we say `cioJsClient`, we are referring to an instance of the [constructorio-client-javascript](https://www.npmjs.com/package/@constructor-io/constructorio-client-javascript)\n\nThe following stories show how different options affect the hook's behavior!\n";
|
|
4
|
+
export declare const hookDescription = "- import `useCioAutocomplete` and call this custom hook in a functional component.\n- This hook leaves rendering logic up to you, while handling:\n - state management\n - data fetching\n - keyboard navigation\n - mouse interactions\n - focus and submit event handling\n - accessibility\n- To use this hook, an `apiKey` or `cioJsClient` are required, and an `onSubmit` callback must be passed to the `useCioAutocomplete` hook to configure behavior. All other values are optional.\n- use the <a href=\"https://kentcdodds.com/blog/how-to-give-rendering-control-to-users-with-prop-getters\" target=\"__blank\">prop getters</a> and other variables returned by this hook (below) to leverage the functionality described above with jsx elements in your react component definitions\n\n> **Important: Please note that all of the select or submit events should go through the `onSubmit` callback. The `getItemProps` hook returns click and keyboard handlers which should be applied to the elements using the spread operator (see code examples below). Adding an `onClick` event manually to the elements would override the default behavior of the library preventing it from sending tracking events and would cause different behavior between click and keyboard events. Selecting an item or submitting the search would both cause `onSubmit` to be called. **\n\nCalling the `useCioAutocomplete` hook returns an object with the following keys:\n\n```jsx\nconst {\n // must be used for a hooks integrations\n query: string, // current input field value\n sections: [{...}], // array of sections data to render in menu list\n totalNumResultsPerSection: {\n \"Products\": number,\n \"Search Suggestions\": number,\n ...\n }, // total number of product and search suggestion results (and other sections)\n getFormProps: () => ({...})), // prop getter for jsx form element\n getInputProps: () => ({...})), // prop getter for jsx input element\n getMenuProps: () => ({...})), // prop getter for jsx element rendering the results container\n getItemProps: (item) => ({...})), // prop getter for jsx element rendering each result\n getSectionProps: (section: Section) => ({...})), // prop getter for jsx element rendering each section.\n\n // available for use, but not required for all use cases\n selectedItem: item, // undefined or current selected item (via hover or arrow keys)\n isOpen: boolean, // current state of the menu list\n openMenu: () => void, // open menu\n closeMenu: () => void, // close menu\n setQuery: () => void, // update the current input field value\n getLabelProps: () => ({...})), // prop getter for a jsx label element\n cioJsClient, // instance of constructorio-client-javascript\n } = useCioAutocomplete(args);\n```\n\n> Note: when we say `cioJsClient`, we are referring to an instance of the [constructorio-client-javascript](https://www.npmjs.com/package/@constructor-io/constructorio-client-javascript)\n\nThe following stories show how different options affect the hook's behavior!\n";
|
|
5
5
|
export declare const sectionsDescription = "- by default, typing a query will fetch data for Search Suggestions and Products\n- to override this, pass an array of sections objects\n- the order of the objects in the `sections` array determines the order of the results\n- each autocomplete section object must have a `indexSectionName`\n- each recommendation section object must have a `podId`\n- each custom section object must have a `displayName`\n- each section object can specify a `type`\n- each section object can override the default `numResults` of 8\n\n`indexSectionName` refers to a section under an index. The default sections are \"Products\" and \"Search Suggestions\". You can find all the sections that exist in your index under the \"Indexes\" tab of Constructor dashboard.\n\nWhen no values are passed for the `sections` argument, the following defaults are used:\n\n```jsx\n[\n {\n indexSectionName: 'Search Suggestions',\n type: 'autocomplete',\n numResults: 8\n },\n {\n indexSectionName: 'Products',\n type: 'autocomplete',\n numResults: 8\n }\n]\n```\n";
|
|
6
6
|
export declare const userEventsDescription = "- pass callback functions to respond to user events\n- if provided, the onFocus callback function will be called each time the user focuses on the text input field\n- if provided, the onChange callback function will be called each time the user changes the value in the text input field\n- the onSubmit callback function will be called each time the user submits the form\n- the user can submit the form by pressing the enter key in the text input field, clicking a submit button within the form, clicking on a result, or pressing enter while a result is selected\n\n> \u26A0\uFE0F NOTE \u26A0\uFE0F Use the Storybook Canvas Actions tab to explore the behavior of all of these `OnEvent` callback functions as you interact with our Default User Events example rendered in the Canvas. In the stories below, Storybook Canvas Actions have been disabled to focus on each of these callback functions in isolation. Each of the example callback functions in the stories below log output to the console tab of the browser's developer tools.";
|
|
7
7
|
export declare const zeroStateDescription = "- when the text input field has no text, we call this zero state\n- by default, the autocomplete shows nothing in the menu it's for zero state\n- to show zero state results, pass an array of section objects for `zeroStateSections`\n- when `zeroStateSections` has sections, the menu will open on user focus by default\n- set `openOnFocus` to false, to only show `zeroStateSections` after user has typed and then cleared the text input, instead of as soon as the user focuses on the text input\n- the order of the objects in the `zeroStateSections` array determines the order of the results\n- each autocomplete section object must have a `indexSectionName`\n- each recommendation section object must have a `podId`\n- each custom section object must have a `displayName`\n- each section object can specify a `type`\n- each section object can override the default `numResults` of 8";
|
|
@@ -4,6 +4,7 @@ export type UseCioAutocompleteOptions = Omit<CioAutocompleteProps, 'children'>;
|
|
|
4
4
|
declare const useCioAutocomplete: (options: UseCioAutocompleteOptions) => {
|
|
5
5
|
query: string;
|
|
6
6
|
sections: Section[];
|
|
7
|
+
totalNumResultsPerSection: Record<string, number>;
|
|
7
8
|
request: Partial<import("@constructor-io/constructorio-client-javascript/lib/types").AutocompleteRequestType>;
|
|
8
9
|
featureToggles: {
|
|
9
10
|
featureDisplaySearchSuggestionImages: boolean;
|
|
@@ -6,4 +6,5 @@ export default function useSections(query: string, cioClient: Nullable<Construct
|
|
|
6
6
|
activeSectionsWithData: Section[];
|
|
7
7
|
zeroStateActiveSections: number | false | undefined;
|
|
8
8
|
request: Partial<import("@constructor-io/constructorio-client-javascript/lib/types").AutocompleteRequestType>;
|
|
9
|
+
totalNumResultsPerSection: Record<string, number>;
|
|
9
10
|
};
|
package/lib/types/types.d.ts
CHANGED
|
@@ -79,6 +79,7 @@ export type SectionsData = {
|
|
|
79
79
|
export type AutocompleteResultSections = {
|
|
80
80
|
sectionsData: SectionsData;
|
|
81
81
|
request: Partial<AutocompleteRequestType>;
|
|
82
|
+
totalNumResultsPerSection: Record<string, number>;
|
|
82
83
|
};
|
|
83
84
|
type SectionType = 'autocomplete' | 'recommendations' | 'custom';
|
|
84
85
|
export type SectionConfiguration = {
|
package/lib/types/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "1.
|
|
1
|
+
declare const _default: "1.22.1";
|
|
2
2
|
export default _default;
|
package/package.json
CHANGED