@constructor-io/constructorio-ui-autocomplete 1.18.1 → 1.19.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.
Files changed (35) hide show
  1. package/README.md +2 -2
  2. package/dist/constructorio-ui-autocomplete-bundled.js +12 -12
  3. package/lib/cjs/components/Autocomplete/AutocompleteResults/AutocompleteResults.js +20 -1
  4. package/lib/cjs/components/Autocomplete/SectionItemsList/SectionItemsList.js +19 -2
  5. package/lib/cjs/constants.js +11 -5
  6. package/lib/cjs/hooks/useCioAutocomplete.js +58 -7
  7. package/lib/cjs/hooks/useDebouncedFetchSections.js +8 -6
  8. package/lib/cjs/hooks/useFetchRecommendationPod.js +3 -3
  9. package/lib/cjs/hooks/useRecommendationsObserver.js +2 -1
  10. package/lib/cjs/hooks/useSections.js +3 -2
  11. package/lib/cjs/typeGuards.js +10 -2
  12. package/lib/cjs/types.js +17 -0
  13. package/lib/cjs/utils.js +24 -12
  14. package/lib/cjs/version.js +1 -1
  15. package/lib/mjs/components/Autocomplete/AutocompleteResults/AutocompleteResults.js +20 -1
  16. package/lib/mjs/components/Autocomplete/SectionItemsList/SectionItemsList.js +19 -2
  17. package/lib/mjs/constants.js +11 -5
  18. package/lib/mjs/hooks/useCioAutocomplete.js +59 -8
  19. package/lib/mjs/hooks/useDebouncedFetchSections.js +8 -6
  20. package/lib/mjs/hooks/useFetchRecommendationPod.js +5 -2
  21. package/lib/mjs/hooks/useRecommendationsObserver.js +2 -1
  22. package/lib/mjs/hooks/useSections.js +3 -2
  23. package/lib/mjs/typeGuards.js +7 -1
  24. package/lib/mjs/types.js +14 -1
  25. package/lib/mjs/utils.js +23 -12
  26. package/lib/mjs/version.js +1 -1
  27. package/lib/types/constants.d.ts +2 -2
  28. package/lib/types/hooks/useDebouncedFetchSections.d.ts +2 -2
  29. package/lib/types/hooks/useFetchRecommendationPod.d.ts +2 -2
  30. package/lib/types/hooks/useSections.d.ts +1 -1
  31. package/lib/types/typeGuards.d.ts +3 -1
  32. package/lib/types/types.d.ts +45 -11
  33. package/lib/types/utils.d.ts +3 -2
  34. package/lib/types/version.d.ts +1 -1
  35. package/package.json +1 -1
@@ -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) => (react_1.default.createElement(SectionItemsList_1.default, { section: section, key: section.identifier })));
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 sectionName = (section === null || section === void 0 ? void 0 : section.displayName) || (section === null || section === void 0 ? void 0 : section.identifier);
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)(sectionName)),
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) {
@@ -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 search suggestions and Products
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 an \`identifier\`
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
- identifier: 'Search Suggestions',
74
+ indexSectionName: 'Search Suggestions',
71
75
  type: 'autocomplete',
72
76
  numResults: 8
73
77
  },
74
78
  {
75
- identifier: 'Products',
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 an \`identifier\`
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
- identifier: 'Search Suggestions',
16
+ indexSectionName: 'Search Suggestions',
16
17
  type: 'autocomplete',
17
18
  },
18
19
  {
19
- identifier: 'Products',
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?', sections = exports.defaultSections, zeroStateSections, autocompleteClassName = 'cio-autocomplete', advancedParameters, defaultInput, } = options;
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 sectionName = (section === null || section === void 0 ? void 0 : section.displayName) || (section === null || section === void 0 ? void 0 : section.identifier);
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: `${sectionName} cio-section`,
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 (section.type === 'recommendations') {
172
+ if ((0, typeGuards_1.isRecommendationsSection)(section)) {
122
173
  attributes['data-cnstrc-recommendations'] = true;
123
- attributes['data-cnstrc-recommendations-pod-id'] = section.identifier;
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.identifier]: (sectionConfig === null || sectionConfig === void 0 ? void 0 : sectionConfig.numResults) || 8 })), {});
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
- const newSectionsData = transformResponse(response, {
55
- numTermsWithGroupSuggestions,
56
- numGroupsSuggestedPerTerm,
57
- });
58
- setSectionsData(newSectionsData);
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 { identifier: podId } = _a, parameters = tslib_1.__rest(_a, ["identifier"]);
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: ((_b = recommendationPods[index]) === null || _b === void 0 ? void 0 : _b.section) || 'Products', podId: pod.id }));
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) => section.type === 'recommendations')
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) => config.type === 'autocomplete' || !config.type), [activeSections]);
16
- const recommendationsSections = (0, react_1.useMemo)(() => activeSections === null || activeSections === void 0 ? void 0 : activeSections.filter((config) => config.type === 'recommendations'), [activeSections]);
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
@@ -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.data !== undefined;
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/types.js CHANGED
@@ -1,2 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isAutocompleteSearchSubmit = exports.isAutocompleteSelectSubmit = void 0;
4
+ /**
5
+ * Checks if the provided event is an AutocompleteSelectSubmit event.
6
+ * @param event The event to check.
7
+ * @returns True if the event is an AutocompleteSelectSubmit event, false otherwise.
8
+ * @example if (isAutocompleteSelectSubmit(event)) { ... } // `query` is available
9
+ */
10
+ const isAutocompleteSelectSubmit = (event) => 'item' in event && 'originalQuery' in event;
11
+ exports.isAutocompleteSelectSubmit = isAutocompleteSelectSubmit;
12
+ /**
13
+ * Checks if the given event is an AutocompleteSearchSubmit event.
14
+ * @param event The event to check.
15
+ * @returns True if the event is an AutocompleteSearchSubmit event, false otherwise.
16
+ * @example if (isAutocompleteSearchSubmit(event)) { ... } // `item` and `originalQuery` are available
17
+ */
18
+ const isAutocompleteSearchSubmit = (event) => 'query' in event;
19
+ exports.isAutocompleteSearchSubmit = isAutocompleteSearchSubmit;
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 { identifier } = sectionConfig;
132
+ const { type } = sectionConfig;
127
133
  let sectionData;
128
- if ((0, typeGuards_1.isCustomSection)(sectionConfig)) {
129
- // Copy id from data to the top level
130
- sectionData = sectionConfig.data.map((item) => {
131
- var _a;
132
- 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) }));
133
- });
134
- }
135
- else {
136
- sectionData = sectionResults[identifier];
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) => section.identifier === target.dataset.cnstrcRecommendationsPodId);
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 ({
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = '1.18.1';
3
+ exports.default = '1.19.1';
@@ -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) => (React.createElement(SectionItemsList, { section: section, key: section.identifier })));
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 sectionName = section?.displayName || section?.identifier;
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(sectionName)),
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) {
@@ -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 search suggestions and Products
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 an \`identifier\`
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
- identifier: 'Search Suggestions',
71
+ indexSectionName: 'Search Suggestions',
68
72
  type: 'autocomplete',
69
73
  numResults: 8
70
74
  },
71
75
  {
72
- identifier: 'Products',
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 an \`identifier\`
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
- identifier: 'Search Suggestions',
12
+ indexSectionName: 'Search Suggestions',
12
13
  type: 'autocomplete',
13
14
  },
14
15
  {
15
- identifier: 'Products',
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?', sections = defaultSections, zeroStateSections, autocompleteClassName = 'cio-autocomplete', advancedParameters, defaultInput, } = options;
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 sectionName = section?.displayName || section?.identifier;
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: `${sectionName} cio-section`,
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.type === 'recommendations') {
182
+ if (isRecommendationsSection(section)) {
132
183
  attributes['data-cnstrc-recommendations'] = true;
133
- attributes['data-cnstrc-recommendations-pod-id'] = section.identifier;
184
+ attributes['data-cnstrc-recommendations-pod-id'] = section.podId;
134
185
  }
135
186
  return attributes;
136
187
  },