@constructor-io/constructorio-ui-autocomplete 1.18.0 → 1.19.0
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/README.md +2 -2
- package/dist/constructorio-ui-autocomplete-bundled.js +12 -12
- package/lib/cjs/components/Autocomplete/AutocompleteResults/AutocompleteResults.js +20 -1
- package/lib/cjs/components/Autocomplete/SectionItemsList/SectionItemsList.js +19 -2
- package/lib/cjs/constants.js +11 -5
- package/lib/cjs/hooks/useCioAutocomplete.js +58 -7
- package/lib/cjs/hooks/useDebouncedFetchSections.js +8 -6
- package/lib/cjs/hooks/useFetchRecommendationPod.js +3 -3
- package/lib/cjs/hooks/useRecommendationsObserver.js +2 -1
- package/lib/cjs/hooks/useSections.js +3 -2
- package/lib/cjs/typeGuards.js +10 -2
- package/lib/cjs/utils.js +24 -12
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/Autocomplete/AutocompleteResults/AutocompleteResults.js +20 -1
- package/lib/mjs/components/Autocomplete/SectionItemsList/SectionItemsList.js +19 -2
- package/lib/mjs/constants.js +11 -5
- package/lib/mjs/hooks/useCioAutocomplete.js +59 -8
- package/lib/mjs/hooks/useDebouncedFetchSections.js +8 -6
- package/lib/mjs/hooks/useFetchRecommendationPod.js +5 -2
- package/lib/mjs/hooks/useRecommendationsObserver.js +2 -1
- package/lib/mjs/hooks/useSections.js +3 -2
- package/lib/mjs/typeGuards.js +7 -1
- package/lib/mjs/utils.js +23 -12
- package/lib/mjs/version.js +1 -1
- package/lib/types/constants.d.ts +2 -2
- package/lib/types/hooks/useDebouncedFetchSections.d.ts +2 -2
- package/lib/types/hooks/useFetchRecommendationPod.d.ts +2 -2
- package/lib/types/hooks/useSections.d.ts +1 -1
- package/lib/types/typeGuards.d.ts +3 -1
- package/lib/types/types.d.ts +21 -9
- package/lib/types/utils.d.ts +3 -2
- package/lib/types/version.d.ts +1 -1
- package/package.json +3 -3
|
@@ -2,9 +2,28 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const react_1 = tslib_1.__importStar(require("react"));
|
|
5
|
+
const utils_1 = require("../../../utils");
|
|
5
6
|
const CioAutocompleteProvider_1 = require("../CioAutocompleteProvider");
|
|
6
7
|
const SectionItemsList_1 = tslib_1.__importDefault(require("../SectionItemsList/SectionItemsList"));
|
|
7
|
-
const DefaultRenderResults = ({ sections }) => sections === null || sections === void 0 ? void 0 : sections.map((section) =>
|
|
8
|
+
const DefaultRenderResults = ({ sections }) => sections === null || sections === void 0 ? void 0 : sections.map((section) => {
|
|
9
|
+
const { type } = section;
|
|
10
|
+
let key = section.displayName;
|
|
11
|
+
switch (type) {
|
|
12
|
+
case 'recommendations':
|
|
13
|
+
key = section.podId;
|
|
14
|
+
break;
|
|
15
|
+
case 'custom':
|
|
16
|
+
key = (0, utils_1.toKebabCase)(section.displayName);
|
|
17
|
+
break;
|
|
18
|
+
case 'autocomplete':
|
|
19
|
+
key = section.indexSectionName;
|
|
20
|
+
break;
|
|
21
|
+
default:
|
|
22
|
+
key = section.indexSectionName;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
return react_1.default.createElement(SectionItemsList_1.default, { section: section, key: key });
|
|
26
|
+
});
|
|
8
27
|
function AutocompleteResults(props) {
|
|
9
28
|
const { children = DefaultRenderResults } = props;
|
|
10
29
|
const { sections, isOpen, getMenuProps, getItemProps } = (0, react_1.useContext)(CioAutocompleteProvider_1.CioAutocompleteContext);
|
|
@@ -9,11 +9,28 @@ const CioAutocompleteProvider_1 = require("../CioAutocompleteProvider");
|
|
|
9
9
|
const DefaultRenderSectionItemsList = function ({ section }) {
|
|
10
10
|
var _a, _b;
|
|
11
11
|
const { getSectionProps } = (0, react_1.useContext)(CioAutocompleteProvider_1.CioAutocompleteContext);
|
|
12
|
-
const
|
|
12
|
+
const { type, displayName } = section;
|
|
13
|
+
let sectionTitle = displayName;
|
|
14
|
+
if (!sectionTitle) {
|
|
15
|
+
switch (type) {
|
|
16
|
+
case 'recommendations':
|
|
17
|
+
sectionTitle = section.podId;
|
|
18
|
+
break;
|
|
19
|
+
case 'autocomplete':
|
|
20
|
+
sectionTitle = section.indexSectionName;
|
|
21
|
+
break;
|
|
22
|
+
case 'custom':
|
|
23
|
+
sectionTitle = section.displayName;
|
|
24
|
+
break;
|
|
25
|
+
default:
|
|
26
|
+
sectionTitle = section.indexSectionName;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
13
30
|
if (!((_a = section === null || section === void 0 ? void 0 : section.data) === null || _a === void 0 ? void 0 : _a.length))
|
|
14
31
|
return null;
|
|
15
32
|
return (react_1.default.createElement("li", Object.assign({}, getSectionProps(section)),
|
|
16
|
-
react_1.default.createElement("h5", { className: 'cio-sectionName', "aria-hidden": true }, (0, utils_1.camelToStartCase)(
|
|
33
|
+
react_1.default.createElement("h5", { className: 'cio-sectionName', "aria-hidden": true }, (0, utils_1.camelToStartCase)(sectionTitle)),
|
|
17
34
|
react_1.default.createElement("ul", { className: 'cio-section-items', role: 'none' }, (_b = section === null || section === void 0 ? void 0 : section.data) === null || _b === void 0 ? void 0 : _b.map((item) => (react_1.default.createElement(SectionItem_1.default, { item: item, key: item === null || item === void 0 ? void 0 : item.id, displaySearchTermHighlights: section.displaySearchTermHighlights }))))));
|
|
18
35
|
};
|
|
19
36
|
function SectionItemsList(props) {
|
package/lib/cjs/constants.js
CHANGED
|
@@ -55,24 +55,28 @@ The following stories show how different options affect the hook's behavior!
|
|
|
55
55
|
/// //////////////////////////////
|
|
56
56
|
// Storybook Pages
|
|
57
57
|
/// //////////////////////////////
|
|
58
|
-
exports.sectionsDescription = `- by default, typing a query will fetch data for
|
|
58
|
+
exports.sectionsDescription = `- by default, typing a query will fetch data for Search Suggestions and Products
|
|
59
59
|
- to override this, pass an array of sections objects
|
|
60
60
|
- the order of the objects in the \`sections\` array determines the order of the results
|
|
61
|
-
- each section object must have
|
|
61
|
+
- each autocomplete section object must have a \`indexSectionName\`
|
|
62
|
+
- each recommendation section object must have a \`podId\`
|
|
63
|
+
- each custom section object must have a \`displayName\`
|
|
62
64
|
- each section object can specify a \`type\`
|
|
63
65
|
- each section object can override the default \`numResults\` of 8
|
|
64
66
|
|
|
67
|
+
\`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.
|
|
68
|
+
|
|
65
69
|
When no values are passed for the \`sections\` argument, the following defaults are used:
|
|
66
70
|
|
|
67
71
|
\`\`\`jsx
|
|
68
72
|
[
|
|
69
73
|
{
|
|
70
|
-
|
|
74
|
+
indexSectionName: 'Search Suggestions',
|
|
71
75
|
type: 'autocomplete',
|
|
72
76
|
numResults: 8
|
|
73
77
|
},
|
|
74
78
|
{
|
|
75
|
-
|
|
79
|
+
indexSectionName: 'Products',
|
|
76
80
|
type: 'autocomplete',
|
|
77
81
|
numResults: 8
|
|
78
82
|
}
|
|
@@ -92,7 +96,9 @@ exports.zeroStateDescription = `- when the text input field has no text, we call
|
|
|
92
96
|
- when \`zeroStateSections\` has sections, the menu will open on user focus by default
|
|
93
97
|
- 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
|
|
94
98
|
- the order of the objects in the \`zeroStateSections\` array determines the order of the results
|
|
95
|
-
- each section object must have
|
|
99
|
+
- each autocomplete section object must have a \`indexSectionName\`
|
|
100
|
+
- each recommendation section object must have a \`podId\`
|
|
101
|
+
- each custom section object must have a \`displayName\`
|
|
96
102
|
- each section object can specify a \`type\`
|
|
97
103
|
- each section object can override the default \`numResults\` of 8`;
|
|
98
104
|
/// //////////////////////////////
|
|
@@ -10,18 +10,48 @@ const utils_1 = require("../utils");
|
|
|
10
10
|
const useConsoleErrors_1 = tslib_1.__importDefault(require("./useConsoleErrors"));
|
|
11
11
|
const useSections_1 = tslib_1.__importDefault(require("./useSections"));
|
|
12
12
|
const useRecommendationsObserver_1 = tslib_1.__importDefault(require("./useRecommendationsObserver"));
|
|
13
|
+
const typeGuards_1 = require("../typeGuards");
|
|
13
14
|
exports.defaultSections = [
|
|
14
15
|
{
|
|
15
|
-
|
|
16
|
+
indexSectionName: 'Search Suggestions',
|
|
16
17
|
type: 'autocomplete',
|
|
17
18
|
},
|
|
18
19
|
{
|
|
19
|
-
|
|
20
|
+
indexSectionName: 'Products',
|
|
20
21
|
type: 'autocomplete',
|
|
21
22
|
},
|
|
22
23
|
];
|
|
24
|
+
const convertLegacyParametersAndAddDefaults = (sections) => sections.map((config) => {
|
|
25
|
+
if ((0, typeGuards_1.isRecommendationsSection)(config)) {
|
|
26
|
+
if (config.identifier && !config.podId) {
|
|
27
|
+
return Object.assign(Object.assign({}, config), { podId: config.identifier });
|
|
28
|
+
}
|
|
29
|
+
if (!config.indexSectionName) {
|
|
30
|
+
return Object.assign(Object.assign({}, config), { indexSectionName: 'Products' });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if ((0, typeGuards_1.isAutocompleteSection)(config)) {
|
|
34
|
+
if (config.identifier && !config.indexSectionName) {
|
|
35
|
+
return Object.assign(Object.assign({}, config), { indexSectionName: config.identifier });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return config;
|
|
39
|
+
});
|
|
23
40
|
const useCioAutocomplete = (options) => {
|
|
24
|
-
const { onSubmit, onChange, openOnFocus, apiKey, cioJsClient, cioJsClientOptions, placeholder = 'What can we help you find today?',
|
|
41
|
+
const { onSubmit, onChange, openOnFocus, apiKey, cioJsClient, cioJsClientOptions, placeholder = 'What can we help you find today?', autocompleteClassName = 'cio-autocomplete', advancedParameters, defaultInput, } = options;
|
|
42
|
+
let { sections = exports.defaultSections, zeroStateSections } = options;
|
|
43
|
+
sections = (0, react_1.useMemo)(() => {
|
|
44
|
+
if (sections) {
|
|
45
|
+
return convertLegacyParametersAndAddDefaults(sections);
|
|
46
|
+
}
|
|
47
|
+
return sections;
|
|
48
|
+
}, [sections]);
|
|
49
|
+
zeroStateSections = (0, react_1.useMemo)(() => {
|
|
50
|
+
if (zeroStateSections) {
|
|
51
|
+
return convertLegacyParametersAndAddDefaults(zeroStateSections);
|
|
52
|
+
}
|
|
53
|
+
return zeroStateSections;
|
|
54
|
+
}, [zeroStateSections]);
|
|
25
55
|
const [query, setQuery] = (0, react_1.useState)(defaultInput || '');
|
|
26
56
|
const previousQuery = (0, usePrevious_1.default)(query);
|
|
27
57
|
const cioClient = (0, useCioClient_1.default)({ apiKey, cioJsClient, cioJsClientOptions });
|
|
@@ -110,17 +140,38 @@ const useCioAutocomplete = (options) => {
|
|
|
110
140
|
}),
|
|
111
141
|
getSectionProps: (section) => {
|
|
112
142
|
var _a;
|
|
113
|
-
const
|
|
143
|
+
const { type, displayName } = section;
|
|
144
|
+
let sectionTitle = displayName;
|
|
145
|
+
// Add the indexSectionName as a class to the section container to make sure it gets the styles
|
|
146
|
+
// Even if the section is a recommendation pod, if the results are "Products" or "Search Suggestions"
|
|
147
|
+
// ... they should be styled accordingly
|
|
148
|
+
const indexSectionName = type !== 'custom' && section.indexSectionName ? (0, utils_1.toKebabCase)(section.indexSectionName) : '';
|
|
149
|
+
if (!sectionTitle) {
|
|
150
|
+
switch (type) {
|
|
151
|
+
case 'recommendations':
|
|
152
|
+
sectionTitle = section.podId;
|
|
153
|
+
break;
|
|
154
|
+
case 'autocomplete':
|
|
155
|
+
sectionTitle = section.indexSectionName;
|
|
156
|
+
break;
|
|
157
|
+
case 'custom':
|
|
158
|
+
sectionTitle = section.displayName;
|
|
159
|
+
break;
|
|
160
|
+
default:
|
|
161
|
+
sectionTitle = section.indexSectionName;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
114
165
|
const attributes = {
|
|
115
|
-
className: `${
|
|
166
|
+
className: `${sectionTitle} cio-section ${indexSectionName}`,
|
|
116
167
|
ref: section.ref,
|
|
117
168
|
role: 'none',
|
|
118
169
|
'data-cnstrc-section': (_a = section.data[0]) === null || _a === void 0 ? void 0 : _a.section,
|
|
119
170
|
};
|
|
120
171
|
// Add data attributes for recommendations
|
|
121
|
-
if (
|
|
172
|
+
if ((0, typeGuards_1.isRecommendationsSection)(section)) {
|
|
122
173
|
attributes['data-cnstrc-recommendations'] = true;
|
|
123
|
-
attributes['data-cnstrc-recommendations-pod-id'] = section.
|
|
174
|
+
attributes['data-cnstrc-recommendations-pod-id'] = section.podId;
|
|
124
175
|
}
|
|
125
176
|
return attributes;
|
|
126
177
|
},
|
|
@@ -42,7 +42,7 @@ const useDebouncedFetchSection = (query, cioClient, autocompleteSections, advanc
|
|
|
42
42
|
// eslint-disable-next-line no-param-reassign
|
|
43
43
|
decoratedParameters === null || decoratedParameters === void 0 ? true : delete decoratedParameters.debounce;
|
|
44
44
|
if (autocompleteSections) {
|
|
45
|
-
decoratedParameters.resultsPerSection = autocompleteSections.reduce((acc, sectionConfig) => (Object.assign(Object.assign({}, acc), { [sectionConfig.
|
|
45
|
+
decoratedParameters.resultsPerSection = autocompleteSections.reduce((acc, sectionConfig) => (Object.assign(Object.assign({}, acc), { [sectionConfig.indexSectionName]: (sectionConfig === null || sectionConfig === void 0 ? void 0 : sectionConfig.numResults) || 8 })), {});
|
|
46
46
|
}
|
|
47
47
|
return decoratedParameters;
|
|
48
48
|
}, [autocompleteSections, advancedParameters]);
|
|
@@ -51,11 +51,13 @@ const useDebouncedFetchSection = (query, cioClient, autocompleteSections, advanc
|
|
|
51
51
|
if (debouncedSearchTerm.trim()) {
|
|
52
52
|
try {
|
|
53
53
|
const response = yield (cioClient === null || cioClient === void 0 ? void 0 : cioClient.autocomplete.getAutocompleteResults(debouncedSearchTerm, autocompleteParameters));
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
if (response) {
|
|
55
|
+
const newSectionsData = transformResponse(response, {
|
|
56
|
+
numTermsWithGroupSuggestions,
|
|
57
|
+
numGroupsSuggestedPerTerm,
|
|
58
|
+
});
|
|
59
|
+
setSectionsData(newSectionsData);
|
|
60
|
+
}
|
|
59
61
|
}
|
|
60
62
|
catch (error) {
|
|
61
63
|
// eslint-disable-next-line no-console
|
|
@@ -9,8 +9,8 @@ const useFetchRecommendationPod = (cioClient, recommendationPods) => {
|
|
|
9
9
|
return;
|
|
10
10
|
const fetchRecommendationResults = () => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
11
11
|
const responses = yield Promise.all(recommendationPods.map((_a) => {
|
|
12
|
-
var {
|
|
13
|
-
return cioClient.recommendations.getRecommendations(podId, parameters);
|
|
12
|
+
var { podId, indexSectionName } = _a, parameters = tslib_1.__rest(_a, ["podId", "indexSectionName"]);
|
|
13
|
+
return cioClient.recommendations.getRecommendations(podId, Object.assign(Object.assign({}, parameters), { section: indexSectionName }));
|
|
14
14
|
}));
|
|
15
15
|
const recommendationPodResults = {};
|
|
16
16
|
responses.forEach(({ response }, index) => {
|
|
@@ -18,7 +18,7 @@ const useFetchRecommendationPod = (cioClient, recommendationPods) => {
|
|
|
18
18
|
if (pod === null || pod === void 0 ? void 0 : pod.id) {
|
|
19
19
|
recommendationPodResults[pod.id] = results === null || results === void 0 ? void 0 : results.map((item) => {
|
|
20
20
|
var _a, _b;
|
|
21
|
-
return (Object.assign(Object.assign({}, item), { id: (_a = item === null || item === void 0 ? void 0 : item.data) === null || _a === void 0 ? void 0 : _a.id, section: (
|
|
21
|
+
return (Object.assign(Object.assign({}, item), { id: (_a = item === null || item === void 0 ? void 0 : item.data) === null || _a === void 0 ? void 0 : _a.id, section: (_b = recommendationPods[index]) === null || _b === void 0 ? void 0 : _b.indexSectionName, podId: pod.id }));
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const react_1 = require("react");
|
|
4
|
+
const typeGuards_1 = require("../typeGuards");
|
|
4
5
|
/**
|
|
5
6
|
* Custom hook that observes the visibility of recommendation sections and calls trackRecommendationView event.
|
|
6
7
|
* This is done by using the IntersectionObserver API to observe the visibility of each recommendation section.
|
|
@@ -16,7 +17,7 @@ const react_1 = require("react");
|
|
|
16
17
|
function useRecommendationsObserver(menuIsOpen, sections, constructorIO, trackRecommendationView) {
|
|
17
18
|
// Get refs for each section
|
|
18
19
|
const refs = sections
|
|
19
|
-
.filter((section) =>
|
|
20
|
+
.filter((section) => (0, typeGuards_1.isRecommendationsSection)(section))
|
|
20
21
|
.map((section) => section.ref);
|
|
21
22
|
(0, react_1.useEffect)(() => {
|
|
22
23
|
const intersectionObserverOptions = {
|
|
@@ -6,14 +6,15 @@ const react_1 = require("react");
|
|
|
6
6
|
const utils_1 = require("../utils");
|
|
7
7
|
const useDebouncedFetchSections_1 = tslib_1.__importDefault(require("./useDebouncedFetchSections"));
|
|
8
8
|
const useFetchRecommendationPod_1 = tslib_1.__importDefault(require("./useFetchRecommendationPod"));
|
|
9
|
+
const typeGuards_1 = require("../typeGuards");
|
|
9
10
|
function useSections(query, cioClient, sections, zeroStateSections, advancedParameters) {
|
|
10
11
|
const zeroStateActiveSections = !query.length && zeroStateSections;
|
|
11
12
|
// Define All Sections
|
|
12
13
|
const activeSections = zeroStateActiveSections ? zeroStateSections : sections;
|
|
13
14
|
const sectionsRefs = (0, react_1.useRef)(activeSections.map(() => (0, react_1.createRef)()));
|
|
14
15
|
const [activeSectionsWithData, setActiveSectionsWithData] = (0, react_1.useState)([]);
|
|
15
|
-
const autocompleteSections = (0, react_1.useMemo)(() => activeSections === null || activeSections === void 0 ? void 0 : activeSections.filter((config) =>
|
|
16
|
-
const recommendationsSections = (0, react_1.useMemo)(() => activeSections === null || activeSections === void 0 ? void 0 : activeSections.filter((config) =>
|
|
16
|
+
const autocompleteSections = (0, react_1.useMemo)(() => activeSections === null || activeSections === void 0 ? void 0 : activeSections.filter((config) => (0, typeGuards_1.isAutocompleteSection)(config)), [activeSections]);
|
|
17
|
+
const recommendationsSections = (0, react_1.useMemo)(() => activeSections === null || activeSections === void 0 ? void 0 : activeSections.filter((config) => (0, typeGuards_1.isRecommendationsSection)(config)), [activeSections]);
|
|
17
18
|
// Fetch Autocomplete Results
|
|
18
19
|
const { sectionsData: autocompleteResults, request } = (0, useDebouncedFetchSections_1.default)(query, cioClient, autocompleteSections, advancedParameters);
|
|
19
20
|
// Fetch Recommendations Results
|
package/lib/cjs/typeGuards.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isCustomSection = exports.isInGroupSuggestion = exports.isSearchSuggestion = exports.isProduct = void 0;
|
|
3
|
+
exports.isRecommendationsSection = exports.isAutocompleteSection = exports.isCustomSection = exports.isInGroupSuggestion = exports.isSearchSuggestion = exports.isProduct = void 0;
|
|
4
4
|
function isProduct(item) {
|
|
5
5
|
return item.section === 'Products';
|
|
6
6
|
}
|
|
@@ -14,6 +14,14 @@ function isInGroupSuggestion(item) {
|
|
|
14
14
|
}
|
|
15
15
|
exports.isInGroupSuggestion = isInGroupSuggestion;
|
|
16
16
|
function isCustomSection(config) {
|
|
17
|
-
return config.
|
|
17
|
+
return config.type === 'custom';
|
|
18
18
|
}
|
|
19
19
|
exports.isCustomSection = isCustomSection;
|
|
20
|
+
function isAutocompleteSection(config) {
|
|
21
|
+
return config.type === 'autocomplete' || !config.type;
|
|
22
|
+
}
|
|
23
|
+
exports.isAutocompleteSection = isAutocompleteSection;
|
|
24
|
+
function isRecommendationsSection(config) {
|
|
25
|
+
return config.type === 'recommendations';
|
|
26
|
+
}
|
|
27
|
+
exports.isRecommendationsSection = isRecommendationsSection;
|
package/lib/cjs/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.translate = exports.getItemsForActiveSections = exports.trackRecommendationView = exports.escapeRegExp = exports.getActiveSectionsWithData = exports.getCioClient = exports.disableStoryActions = exports.stringifyWithDefaults = exports.functionStrings = exports.getStoryParams = exports.sleep = exports.clearConstructorRequests = exports.isTrackingRequestSent = exports.camelToStartCase = exports.getItemPosition = exports.getSearchSuggestionFeatures = void 0;
|
|
3
|
+
exports.translate = exports.getItemsForActiveSections = exports.trackRecommendationView = exports.escapeRegExp = exports.getActiveSectionsWithData = exports.getCioClient = exports.disableStoryActions = exports.stringifyWithDefaults = exports.functionStrings = exports.getStoryParams = exports.sleep = exports.clearConstructorRequests = exports.isTrackingRequestSent = exports.toKebabCase = exports.camelToStartCase = exports.getItemPosition = exports.getSearchSuggestionFeatures = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const constructorio_client_javascript_1 = tslib_1.__importDefault(require("@constructor-io/constructorio-client-javascript"));
|
|
6
6
|
const typeGuards_1 = require("./typeGuards");
|
|
@@ -44,6 +44,12 @@ const camelToStartCase = (camelCaseString) => camelCaseString
|
|
|
44
44
|
// uppercase the first character
|
|
45
45
|
.replace(/^./, (str) => str.toUpperCase());
|
|
46
46
|
exports.camelToStartCase = camelToStartCase;
|
|
47
|
+
const toKebabCase = (str) => str
|
|
48
|
+
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
|
|
49
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
50
|
+
.replace(/[\s_]+/g, '-')
|
|
51
|
+
.toLowerCase();
|
|
52
|
+
exports.toKebabCase = toKebabCase;
|
|
47
53
|
function isTrackingRequestSent(trackingRequestUrl) {
|
|
48
54
|
var _a, _b;
|
|
49
55
|
// eslint-disable-next-line
|
|
@@ -123,17 +129,22 @@ exports.getCioClient = getCioClient;
|
|
|
123
129
|
const getActiveSectionsWithData = (activeSections, sectionResults, sectionsRefs) => {
|
|
124
130
|
const activeSectionsWithData = [];
|
|
125
131
|
activeSections === null || activeSections === void 0 ? void 0 : activeSections.forEach((sectionConfig, index) => {
|
|
126
|
-
const {
|
|
132
|
+
const { type } = sectionConfig;
|
|
127
133
|
let sectionData;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
switch (type) {
|
|
135
|
+
case 'recommendations':
|
|
136
|
+
sectionData = sectionResults[sectionConfig.podId];
|
|
137
|
+
break;
|
|
138
|
+
case 'custom':
|
|
139
|
+
// Copy id from data to the top level
|
|
140
|
+
sectionData = sectionConfig.data.map((item) => {
|
|
141
|
+
var _a;
|
|
142
|
+
return (Object.assign(Object.assign({}, item), { id: (item === null || item === void 0 ? void 0 : item.id) || ((_a = item === null || item === void 0 ? void 0 : item.data) === null || _a === void 0 ? void 0 : _a.id) }));
|
|
143
|
+
});
|
|
144
|
+
break;
|
|
145
|
+
default:
|
|
146
|
+
// Autocomplete
|
|
147
|
+
sectionData = sectionResults[sectionConfig.indexSectionName];
|
|
137
148
|
}
|
|
138
149
|
if (Array.isArray(sectionData)) {
|
|
139
150
|
const section = Object.assign(Object.assign({}, sectionConfig), { data: sectionData });
|
|
@@ -153,7 +164,8 @@ exports.escapeRegExp = escapeRegExp;
|
|
|
153
164
|
const trackRecommendationView = (target, activeSectionsWithData, cioClient) => {
|
|
154
165
|
if (target.dataset.cnstrcRecommendationsPodId) {
|
|
155
166
|
// Pull recommendations from activeSectionsWithData by podId surfaced on target
|
|
156
|
-
const recommendationSection = activeSectionsWithData.find((section) =>
|
|
167
|
+
const recommendationSection = activeSectionsWithData.find((section) => (0, typeGuards_1.isRecommendationsSection)(section) &&
|
|
168
|
+
section.podId === target.dataset.cnstrcRecommendationsPodId);
|
|
157
169
|
const recommendationItems = recommendationSection === null || recommendationSection === void 0 ? void 0 : recommendationSection.data.map((item) => {
|
|
158
170
|
var _a, _b;
|
|
159
171
|
return ({
|
package/lib/cjs/version.js
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
|
+
import { toKebabCase } from '../../../utils';
|
|
2
3
|
import { CioAutocompleteContext } from '../CioAutocompleteProvider';
|
|
3
4
|
import SectionItemsList from '../SectionItemsList/SectionItemsList';
|
|
4
|
-
const DefaultRenderResults = ({ sections }) => sections?.map((section) =>
|
|
5
|
+
const DefaultRenderResults = ({ sections }) => sections?.map((section) => {
|
|
6
|
+
const { type } = section;
|
|
7
|
+
let key = section.displayName;
|
|
8
|
+
switch (type) {
|
|
9
|
+
case 'recommendations':
|
|
10
|
+
key = section.podId;
|
|
11
|
+
break;
|
|
12
|
+
case 'custom':
|
|
13
|
+
key = toKebabCase(section.displayName);
|
|
14
|
+
break;
|
|
15
|
+
case 'autocomplete':
|
|
16
|
+
key = section.indexSectionName;
|
|
17
|
+
break;
|
|
18
|
+
default:
|
|
19
|
+
key = section.indexSectionName;
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
return React.createElement(SectionItemsList, { section: section, key: key });
|
|
23
|
+
});
|
|
5
24
|
export default function AutocompleteResults(props) {
|
|
6
25
|
const { children = DefaultRenderResults } = props;
|
|
7
26
|
const { sections, isOpen, getMenuProps, getItemProps } = useContext(CioAutocompleteContext);
|
|
@@ -5,11 +5,28 @@ import { CioAutocompleteContext } from '../CioAutocompleteProvider';
|
|
|
5
5
|
// eslint-disable-next-line func-names
|
|
6
6
|
const DefaultRenderSectionItemsList = function ({ section }) {
|
|
7
7
|
const { getSectionProps } = useContext(CioAutocompleteContext);
|
|
8
|
-
const
|
|
8
|
+
const { type, displayName } = section;
|
|
9
|
+
let sectionTitle = displayName;
|
|
10
|
+
if (!sectionTitle) {
|
|
11
|
+
switch (type) {
|
|
12
|
+
case 'recommendations':
|
|
13
|
+
sectionTitle = section.podId;
|
|
14
|
+
break;
|
|
15
|
+
case 'autocomplete':
|
|
16
|
+
sectionTitle = section.indexSectionName;
|
|
17
|
+
break;
|
|
18
|
+
case 'custom':
|
|
19
|
+
sectionTitle = section.displayName;
|
|
20
|
+
break;
|
|
21
|
+
default:
|
|
22
|
+
sectionTitle = section.indexSectionName;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
9
26
|
if (!section?.data?.length)
|
|
10
27
|
return null;
|
|
11
28
|
return (React.createElement("li", { ...getSectionProps(section) },
|
|
12
|
-
React.createElement("h5", { className: 'cio-sectionName', "aria-hidden": true }, camelToStartCase(
|
|
29
|
+
React.createElement("h5", { className: 'cio-sectionName', "aria-hidden": true }, camelToStartCase(sectionTitle)),
|
|
13
30
|
React.createElement("ul", { className: 'cio-section-items', role: 'none' }, section?.data?.map((item) => (React.createElement(SectionItem, { item: item, key: item?.id, displaySearchTermHighlights: section.displaySearchTermHighlights }))))));
|
|
14
31
|
};
|
|
15
32
|
export default function SectionItemsList(props) {
|
package/lib/mjs/constants.js
CHANGED
|
@@ -52,24 +52,28 @@ The following stories show how different options affect the hook's behavior!
|
|
|
52
52
|
/// //////////////////////////////
|
|
53
53
|
// Storybook Pages
|
|
54
54
|
/// //////////////////////////////
|
|
55
|
-
export const sectionsDescription = `- by default, typing a query will fetch data for
|
|
55
|
+
export const sectionsDescription = `- by default, typing a query will fetch data for Search Suggestions and Products
|
|
56
56
|
- to override this, pass an array of sections objects
|
|
57
57
|
- the order of the objects in the \`sections\` array determines the order of the results
|
|
58
|
-
- each section object must have
|
|
58
|
+
- each autocomplete section object must have a \`indexSectionName\`
|
|
59
|
+
- each recommendation section object must have a \`podId\`
|
|
60
|
+
- each custom section object must have a \`displayName\`
|
|
59
61
|
- each section object can specify a \`type\`
|
|
60
62
|
- each section object can override the default \`numResults\` of 8
|
|
61
63
|
|
|
64
|
+
\`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.
|
|
65
|
+
|
|
62
66
|
When no values are passed for the \`sections\` argument, the following defaults are used:
|
|
63
67
|
|
|
64
68
|
\`\`\`jsx
|
|
65
69
|
[
|
|
66
70
|
{
|
|
67
|
-
|
|
71
|
+
indexSectionName: 'Search Suggestions',
|
|
68
72
|
type: 'autocomplete',
|
|
69
73
|
numResults: 8
|
|
70
74
|
},
|
|
71
75
|
{
|
|
72
|
-
|
|
76
|
+
indexSectionName: 'Products',
|
|
73
77
|
type: 'autocomplete',
|
|
74
78
|
numResults: 8
|
|
75
79
|
}
|
|
@@ -89,7 +93,9 @@ export const zeroStateDescription = `- when the text input field has no text, we
|
|
|
89
93
|
- when \`zeroStateSections\` has sections, the menu will open on user focus by default
|
|
90
94
|
- 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
|
|
91
95
|
- the order of the objects in the \`zeroStateSections\` array determines the order of the results
|
|
92
|
-
- each section object must have
|
|
96
|
+
- each autocomplete section object must have a \`indexSectionName\`
|
|
97
|
+
- each recommendation section object must have a \`podId\`
|
|
98
|
+
- each custom section object must have a \`displayName\`
|
|
93
99
|
- each section object can specify a \`type\`
|
|
94
100
|
- each section object can override the default \`numResults\` of 8`;
|
|
95
101
|
/// //////////////////////////////
|
|
@@ -2,22 +2,52 @@ import { useMemo, useState } from 'react';
|
|
|
2
2
|
import useCioClient from './useCioClient';
|
|
3
3
|
import useDownShift from './useDownShift';
|
|
4
4
|
import usePrevious from './usePrevious';
|
|
5
|
-
import { getItemPosition, getItemsForActiveSections, getSearchSuggestionFeatures, trackRecommendationView, } from '../utils';
|
|
5
|
+
import { getItemPosition, getItemsForActiveSections, getSearchSuggestionFeatures, trackRecommendationView, toKebabCase, } from '../utils';
|
|
6
6
|
import useConsoleErrors from './useConsoleErrors';
|
|
7
7
|
import useSections from './useSections';
|
|
8
8
|
import useRecommendationsObserver from './useRecommendationsObserver';
|
|
9
|
+
import { isAutocompleteSection, isRecommendationsSection } from '../typeGuards';
|
|
9
10
|
export const defaultSections = [
|
|
10
11
|
{
|
|
11
|
-
|
|
12
|
+
indexSectionName: 'Search Suggestions',
|
|
12
13
|
type: 'autocomplete',
|
|
13
14
|
},
|
|
14
15
|
{
|
|
15
|
-
|
|
16
|
+
indexSectionName: 'Products',
|
|
16
17
|
type: 'autocomplete',
|
|
17
18
|
},
|
|
18
19
|
];
|
|
20
|
+
const convertLegacyParametersAndAddDefaults = (sections) => sections.map((config) => {
|
|
21
|
+
if (isRecommendationsSection(config)) {
|
|
22
|
+
if (config.identifier && !config.podId) {
|
|
23
|
+
return { ...config, podId: config.identifier };
|
|
24
|
+
}
|
|
25
|
+
if (!config.indexSectionName) {
|
|
26
|
+
return { ...config, indexSectionName: 'Products' };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (isAutocompleteSection(config)) {
|
|
30
|
+
if (config.identifier && !config.indexSectionName) {
|
|
31
|
+
return { ...config, indexSectionName: config.identifier };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return config;
|
|
35
|
+
});
|
|
19
36
|
const useCioAutocomplete = (options) => {
|
|
20
|
-
const { onSubmit, onChange, openOnFocus, apiKey, cioJsClient, cioJsClientOptions, placeholder = 'What can we help you find today?',
|
|
37
|
+
const { onSubmit, onChange, openOnFocus, apiKey, cioJsClient, cioJsClientOptions, placeholder = 'What can we help you find today?', autocompleteClassName = 'cio-autocomplete', advancedParameters, defaultInput, } = options;
|
|
38
|
+
let { sections = defaultSections, zeroStateSections } = options;
|
|
39
|
+
sections = useMemo(() => {
|
|
40
|
+
if (sections) {
|
|
41
|
+
return convertLegacyParametersAndAddDefaults(sections);
|
|
42
|
+
}
|
|
43
|
+
return sections;
|
|
44
|
+
}, [sections]);
|
|
45
|
+
zeroStateSections = useMemo(() => {
|
|
46
|
+
if (zeroStateSections) {
|
|
47
|
+
return convertLegacyParametersAndAddDefaults(zeroStateSections);
|
|
48
|
+
}
|
|
49
|
+
return zeroStateSections;
|
|
50
|
+
}, [zeroStateSections]);
|
|
21
51
|
const [query, setQuery] = useState(defaultInput || '');
|
|
22
52
|
const previousQuery = usePrevious(query);
|
|
23
53
|
const cioClient = useCioClient({ apiKey, cioJsClient, cioJsClientOptions });
|
|
@@ -120,17 +150,38 @@ const useCioAutocomplete = (options) => {
|
|
|
120
150
|
'data-testid': 'cio-form',
|
|
121
151
|
}),
|
|
122
152
|
getSectionProps: (section) => {
|
|
123
|
-
const
|
|
153
|
+
const { type, displayName } = section;
|
|
154
|
+
let sectionTitle = displayName;
|
|
155
|
+
// Add the indexSectionName as a class to the section container to make sure it gets the styles
|
|
156
|
+
// Even if the section is a recommendation pod, if the results are "Products" or "Search Suggestions"
|
|
157
|
+
// ... they should be styled accordingly
|
|
158
|
+
const indexSectionName = type !== 'custom' && section.indexSectionName ? toKebabCase(section.indexSectionName) : '';
|
|
159
|
+
if (!sectionTitle) {
|
|
160
|
+
switch (type) {
|
|
161
|
+
case 'recommendations':
|
|
162
|
+
sectionTitle = section.podId;
|
|
163
|
+
break;
|
|
164
|
+
case 'autocomplete':
|
|
165
|
+
sectionTitle = section.indexSectionName;
|
|
166
|
+
break;
|
|
167
|
+
case 'custom':
|
|
168
|
+
sectionTitle = section.displayName;
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
sectionTitle = section.indexSectionName;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
124
175
|
const attributes = {
|
|
125
|
-
className: `${
|
|
176
|
+
className: `${sectionTitle} cio-section ${indexSectionName}`,
|
|
126
177
|
ref: section.ref,
|
|
127
178
|
role: 'none',
|
|
128
179
|
'data-cnstrc-section': section.data[0]?.section,
|
|
129
180
|
};
|
|
130
181
|
// Add data attributes for recommendations
|
|
131
|
-
if (section
|
|
182
|
+
if (isRecommendationsSection(section)) {
|
|
132
183
|
attributes['data-cnstrc-recommendations'] = true;
|
|
133
|
-
attributes['data-cnstrc-recommendations-pod-id'] = section.
|
|
184
|
+
attributes['data-cnstrc-recommendations-pod-id'] = section.podId;
|
|
134
185
|
}
|
|
135
186
|
return attributes;
|
|
136
187
|
},
|
|
@@ -47,7 +47,7 @@ const useDebouncedFetchSection = (query, cioClient, autocompleteSections, advanc
|
|
|
47
47
|
if (autocompleteSections) {
|
|
48
48
|
decoratedParameters.resultsPerSection = autocompleteSections.reduce((acc, sectionConfig) => ({
|
|
49
49
|
...acc,
|
|
50
|
-
[sectionConfig.
|
|
50
|
+
[sectionConfig.indexSectionName]: sectionConfig?.numResults || 8,
|
|
51
51
|
}), {});
|
|
52
52
|
}
|
|
53
53
|
return decoratedParameters;
|
|
@@ -57,11 +57,13 @@ const useDebouncedFetchSection = (query, cioClient, autocompleteSections, advanc
|
|
|
57
57
|
if (debouncedSearchTerm.trim()) {
|
|
58
58
|
try {
|
|
59
59
|
const response = await cioClient?.autocomplete.getAutocompleteResults(debouncedSearchTerm, autocompleteParameters);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
if (response) {
|
|
61
|
+
const newSectionsData = transformResponse(response, {
|
|
62
|
+
numTermsWithGroupSuggestions,
|
|
63
|
+
numGroupsSuggestedPerTerm,
|
|
64
|
+
});
|
|
65
|
+
setSectionsData(newSectionsData);
|
|
66
|
+
}
|
|
65
67
|
}
|
|
66
68
|
catch (error) {
|
|
67
69
|
// eslint-disable-next-line no-console
|