@constructor-io/constructorio-ui-autocomplete 1.28.2 → 1.29.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 +44 -69
- package/dist/constructorio-ui-autocomplete-bundled.js +11 -11
- package/lib/cjs/components/Autocomplete/SearchInput/SearchInput.js +3 -2
- package/lib/cjs/components/Autocomplete/SectionItemsList/SectionItemsList.js +2 -1
- package/lib/cjs/hooks/useCioAutocomplete.js +18 -13
- package/lib/cjs/hooks/useFetchRecommendationPod.js +4 -1
- package/lib/cjs/hooks/useSections/index.js +1 -0
- package/lib/cjs/utils/dataAttributeHelpers.js +141 -0
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/Autocomplete/SearchInput/SearchInput.js +2 -1
- package/lib/mjs/components/Autocomplete/SectionItemsList/SectionItemsList.js +2 -1
- package/lib/mjs/hooks/useCioAutocomplete.js +16 -16
- package/lib/mjs/hooks/useFetchRecommendationPod.js +3 -1
- package/lib/mjs/hooks/useSections/index.js +1 -0
- package/lib/mjs/utils/dataAttributeHelpers.js +133 -0
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/Autocomplete/CioAutocompleteProvider.d.ts +3 -1
- package/lib/types/hooks/useCioAutocomplete.d.ts +3 -1
- package/lib/types/hooks/useSections/index.d.ts +2 -1
- package/lib/types/types.d.ts +2 -0
- package/lib/types/utils/dataAttributeHelpers.d.ts +75 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const react_1 = tslib_1.__importStar(require("react"));
|
|
5
5
|
const CioAutocompleteProvider_1 = require("../CioAutocompleteProvider");
|
|
6
|
+
const dataAttributeHelpers_1 = require("../../../utils/dataAttributeHelpers");
|
|
6
7
|
function DefaultRenderInput({ getFormProps, getInputProps, getLabelProps, setQuery }) {
|
|
7
8
|
const inputProps = getInputProps();
|
|
8
9
|
return (react_1.default.createElement("form", Object.assign({}, getFormProps()),
|
|
@@ -17,7 +18,7 @@ function DefaultRenderInput({ getFormProps, getInputProps, getLabelProps, setQue
|
|
|
17
18
|
react_1.default.createElement("div", { className: 'cio-icon' },
|
|
18
19
|
react_1.default.createElement("svg", { stroke: 'currentColor', fill: 'currentColor', strokeWidth: '0', viewBox: '0 0 512 512', height: '1em', width: '1em', xmlns: 'http://www.w3.org/2000/svg' },
|
|
19
20
|
react_1.default.createElement("path", { d: 'M289.94 256l95-95A24 24 0 00351 127l-95 95-95-95a24 24 0 00-34 34l95 95-95 95a24 24 0 1034 34l95-95 95 95a24 24 0 0034-34z' })))),
|
|
20
|
-
react_1.default.createElement("button", { className: 'cio-submit-btn', "data-testid": 'cio-submit-btn',
|
|
21
|
+
react_1.default.createElement("button", Object.assign({ className: 'cio-submit-btn', "data-testid": 'cio-submit-btn' }, { [dataAttributeHelpers_1.cnstrcDataAttrs.autocomplete.searchSubmitButton]: '' }, { onClick: (e) => {
|
|
21
22
|
var _a;
|
|
22
23
|
if (!inputProps.value) {
|
|
23
24
|
e.preventDefault();
|
|
@@ -25,7 +26,7 @@ function DefaultRenderInput({ getFormProps, getInputProps, getLabelProps, setQue
|
|
|
25
26
|
(_a = document.getElementById(inputProps.id)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
|
-
}, type: 'submit', "aria-label": 'Submit Search' },
|
|
29
|
+
}, type: 'submit', "aria-label": 'Submit Search' }),
|
|
29
30
|
react_1.default.createElement("div", { className: 'cio-icon' },
|
|
30
31
|
react_1.default.createElement("svg", { stroke: 'currentColor', fill: 'currentColor', strokeWidth: '0', viewBox: '0 0 512 512', height: '1em', width: '1em', xmlns: 'http://www.w3.org/2000/svg' },
|
|
31
32
|
react_1.default.createElement("path", { d: 'M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z' }))))));
|
|
@@ -8,6 +8,7 @@ const helpers_1 = require("../../../utils/helpers");
|
|
|
8
8
|
const format_1 = require("../../../utils/format");
|
|
9
9
|
const CioAutocompleteProvider_1 = require("../CioAutocompleteProvider");
|
|
10
10
|
const NoResults_1 = tslib_1.__importDefault(require("../AutocompleteResults/NoResults"));
|
|
11
|
+
const dataAttributeHelpers_1 = require("../../../utils/dataAttributeHelpers");
|
|
11
12
|
// eslint-disable-next-line func-names
|
|
12
13
|
const DefaultRenderSectionItemsList = function ({ section }) {
|
|
13
14
|
var _a, _b, _c;
|
|
@@ -52,7 +53,7 @@ const DefaultRenderSectionItemsList = function ({ section }) {
|
|
|
52
53
|
displayShowAllResultsButton &&
|
|
53
54
|
(typeof type === 'undefined' || type === 'autocomplete') &&
|
|
54
55
|
section.indexSectionName === 'Products' && (react_1.default.createElement("div", { className: 'cio-section-footer' },
|
|
55
|
-
react_1.default.createElement("button", {
|
|
56
|
+
react_1.default.createElement("button", Object.assign({}, { [dataAttributeHelpers_1.cnstrcDataAttrs.autocomplete.searchSubmitButton]: '' }, { className: 'cio-show-all-results-button', type: 'button', onClick: onSubmit }), (0, helpers_1.translate)('show all results', translations))))));
|
|
56
57
|
};
|
|
57
58
|
function SectionItemsList(props) {
|
|
58
59
|
const { section, children = DefaultRenderSectionItemsList } = props;
|
|
@@ -17,6 +17,7 @@ const useRecommendationsObserver_1 = tslib_1.__importDefault(require("./useRecom
|
|
|
17
17
|
const typeGuards_1 = require("../typeGuards");
|
|
18
18
|
const useNormalizedProps_1 = tslib_1.__importDefault(require("./useNormalizedProps"));
|
|
19
19
|
const useCustomBlur_1 = tslib_1.__importDefault(require("./useCustomBlur"));
|
|
20
|
+
const dataAttributeHelpers_1 = require("../utils/dataAttributeHelpers");
|
|
20
21
|
const shopifyDefaults_1 = require("../utils/shopifyDefaults");
|
|
21
22
|
exports.defaultSections = [
|
|
22
23
|
{
|
|
@@ -54,7 +55,7 @@ const useCioAutocomplete = (options) => {
|
|
|
54
55
|
cioJsClientOptions: options.cioJsClientOptions,
|
|
55
56
|
});
|
|
56
57
|
// Get autocomplete sections (autocomplete + recommendations + custom)
|
|
57
|
-
const { fetchRecommendationResults, activeSections, activeSectionsWithData, zeroStateActiveSections, request, totalNumResultsPerSection, } = (0, useSections_1.default)(query, cioClient, sections, zeroStateSections, advancedParameters);
|
|
58
|
+
const { fetchRecommendationResults, activeSections, activeSectionsWithData, zeroStateActiveSections, request, totalNumResultsPerSection, podsData, } = (0, useSections_1.default)(query, cioClient, sections, zeroStateSections, advancedParameters);
|
|
58
59
|
const features = (0, react_1.useMemo)(() => (0, features_1.getFeatures)(request), [request]);
|
|
59
60
|
// Get dropdown items array from active sections (autocomplete + recommendations + custom)
|
|
60
61
|
const items = (0, react_1.useMemo)(() => (0, helpers_1.getItemsForActiveSections)(activeSectionsWithData), [activeSectionsWithData]);
|
|
@@ -75,12 +76,12 @@ const useCioAutocomplete = (options) => {
|
|
|
75
76
|
request,
|
|
76
77
|
featureToggles: features,
|
|
77
78
|
isOpen,
|
|
78
|
-
getMenuProps: () => (Object.assign(Object.assign({}, getMenuProps()), { className: 'cio-results', 'data-testid': 'cio-results',
|
|
79
|
+
getMenuProps: () => (Object.assign(Object.assign({}, getMenuProps()), { className: 'cio-results', 'data-testid': 'cio-results', [dataAttributeHelpers_1.cnstrcDataAttrs.autocomplete.autocompleteContainer]: '' })),
|
|
79
80
|
getLabelProps,
|
|
80
81
|
openMenu,
|
|
81
82
|
closeMenu,
|
|
82
83
|
getItemProps: (item) => {
|
|
83
|
-
var _a
|
|
84
|
+
var _a;
|
|
84
85
|
const { index, sectionId } = (0, helpers_1.getItemPosition)({ item, items });
|
|
85
86
|
const sectionItemTestId = `cio-item-${sectionId === null || sectionId === void 0 ? void 0 : sectionId.replace(' ', '')}`;
|
|
86
87
|
// Products always have links, Search Suggestions with getSearchResultsUrl have links
|
|
@@ -97,9 +98,11 @@ const useCioAutocomplete = (options) => {
|
|
|
97
98
|
}
|
|
98
99
|
},
|
|
99
100
|
};
|
|
100
|
-
|
|
101
|
+
// Get all data attributes using the helper function
|
|
102
|
+
const dataAttributes = (0, dataAttributeHelpers_1.getItemCnstrcDataAttributes)(item);
|
|
103
|
+
return Object.assign(Object.assign(Object.assign(Object.assign({}, getItemPropsDownShift({ item, index })), {
|
|
101
104
|
// @deprecated `sectionItemTestId` will be removed as a className in the next major version
|
|
102
|
-
className: `cio-item ${sectionItemTestId}`, 'data-testid': sectionItemTestId
|
|
105
|
+
className: `cio-item ${sectionItemTestId}`, 'data-testid': sectionItemTestId }), dataAttributes), (hasLink ? {} : nonInteractiveItemsProps));
|
|
103
106
|
},
|
|
104
107
|
getInputProps: () => (Object.assign(Object.assign({}, getInputProps({
|
|
105
108
|
onChange: (e) => {
|
|
@@ -131,7 +134,7 @@ const useCioAutocomplete = (options) => {
|
|
|
131
134
|
// eslint-disable-next-line no-console
|
|
132
135
|
console.log(error);
|
|
133
136
|
}
|
|
134
|
-
}, className: 'cio-input', 'data-testid': 'cio-input',
|
|
137
|
+
}, className: 'cio-input', 'data-testid': 'cio-input', [dataAttributeHelpers_1.cnstrcDataAttrs.autocomplete.input]: '', placeholder, onKeyDownCapture: ({ code, key, nativeEvent }) => {
|
|
135
138
|
const isEnter = code === 'Enter' || key === 'Enter';
|
|
136
139
|
const isHome = code === 'Home' || key === 'Home';
|
|
137
140
|
const isEnd = code === 'End' || key === 'End';
|
|
@@ -170,7 +173,7 @@ const useCioAutocomplete = (options) => {
|
|
|
170
173
|
},
|
|
171
174
|
className: 'cio-form',
|
|
172
175
|
'data-testid': 'cio-form',
|
|
173
|
-
|
|
176
|
+
[dataAttributeHelpers_1.cnstrcDataAttrs.autocomplete.inputForm]: '',
|
|
174
177
|
}),
|
|
175
178
|
getSectionProps: (section) => {
|
|
176
179
|
var _a, _b;
|
|
@@ -207,16 +210,18 @@ const useCioAutocomplete = (options) => {
|
|
|
207
210
|
className: `cio-section cio-section-${sectionListingType} ${getDeprecatedClassNames()}`,
|
|
208
211
|
ref: section.ref,
|
|
209
212
|
role: 'none',
|
|
210
|
-
|
|
213
|
+
[dataAttributeHelpers_1.cnstrcDataAttrs.common.section]: (_b = section.data[0]) === null || _b === void 0 ? void 0 : _b.section,
|
|
211
214
|
};
|
|
212
215
|
if ((0, typeGuards_1.isCustomSection)(section)) {
|
|
213
|
-
attributes[
|
|
214
|
-
attributes[
|
|
216
|
+
attributes[dataAttributeHelpers_1.cnstrcDataAttrs.custom.customSection] = true;
|
|
217
|
+
attributes[dataAttributeHelpers_1.cnstrcDataAttrs.custom.customSectionName] = section.displayName;
|
|
215
218
|
}
|
|
216
|
-
// Add data attributes for recommendations
|
|
219
|
+
// Add data attributes for recommendations using helper
|
|
217
220
|
if ((0, typeGuards_1.isRecommendationsSection)(section)) {
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
const podData = podsData === null || podsData === void 0 ? void 0 : podsData[section.podId];
|
|
222
|
+
const seedItems = (0, dataAttributeHelpers_1.normalizeSeedItems)(section.itemIds);
|
|
223
|
+
const recommendationAttributes = (0, dataAttributeHelpers_1.getRecommendationsSectionCnstrcDataAttributes)(section, podData === null || podData === void 0 ? void 0 : podData.resultId, podData === null || podData === void 0 ? void 0 : podData.numResults, seedItems);
|
|
224
|
+
Object.assign(attributes, recommendationAttributes);
|
|
220
225
|
}
|
|
221
226
|
return attributes;
|
|
222
227
|
},
|
|
@@ -14,7 +14,8 @@ const useFetchRecommendationPod = (cioClient, recommendationPods, fetchZeroState
|
|
|
14
14
|
}));
|
|
15
15
|
const recommendationsPodResults = {};
|
|
16
16
|
const recommendationsPodsData = {};
|
|
17
|
-
responses.forEach(({ response, request }, index) => {
|
|
17
|
+
responses.forEach(({ response, request, result_id: resultId }, index) => {
|
|
18
|
+
var _a;
|
|
18
19
|
const { pod, results } = response;
|
|
19
20
|
if (pod === null || pod === void 0 ? void 0 : pod.id) {
|
|
20
21
|
recommendationsPodResults[pod.id] = results === null || results === void 0 ? void 0 : results.map((item) => {
|
|
@@ -25,6 +26,8 @@ const useFetchRecommendationPod = (cioClient, recommendationPods, fetchZeroState
|
|
|
25
26
|
displayName: pod.display_name,
|
|
26
27
|
podId: pod.id,
|
|
27
28
|
request,
|
|
29
|
+
resultId,
|
|
30
|
+
numResults: (_a = response.total_num_results) !== null && _a !== void 0 ? _a : 0,
|
|
28
31
|
};
|
|
29
32
|
}
|
|
30
33
|
});
|
|
@@ -33,6 +33,7 @@ function useSections(query, cioClient, sections, zeroStateSections, advancedPara
|
|
|
33
33
|
zeroStateActiveSections: showZeroStateSections,
|
|
34
34
|
request: autocomplete.request,
|
|
35
35
|
totalNumResultsPerSection: autocomplete.totalNumResultsPerSection,
|
|
36
|
+
podsData: recommendations.podsData,
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
exports.default = useSections;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getConversionButtonCnstrcDataAttributes = exports.getRecommendationsSectionCnstrcDataAttributes = exports.getItemCnstrcDataAttributes = exports.normalizeSeedItems = exports.cnstrcDataAttrs = void 0;
|
|
4
|
+
const typeGuards_1 = require("../typeGuards");
|
|
5
|
+
exports.cnstrcDataAttrs = {
|
|
6
|
+
common: {
|
|
7
|
+
itemId: 'data-cnstrc-item-id',
|
|
8
|
+
itemName: 'data-cnstrc-item-name',
|
|
9
|
+
itemPrice: 'data-cnstrc-item-price',
|
|
10
|
+
variationId: 'data-cnstrc-item-variation-id',
|
|
11
|
+
numResults: 'data-cnstrc-num-results',
|
|
12
|
+
conversionBtn: 'data-cnstrc-btn',
|
|
13
|
+
resultId: 'data-cnstrc-result-id',
|
|
14
|
+
itemSection: 'data-cnstrc-item-section',
|
|
15
|
+
itemGroup: 'data-cnstrc-item-group',
|
|
16
|
+
slCampaignId: 'data-cnstrc-sl-campaign-id',
|
|
17
|
+
slCampaignOwner: 'data-cnstrc-sl-campaign-owner',
|
|
18
|
+
section: 'data-cnstrc-section',
|
|
19
|
+
},
|
|
20
|
+
autocomplete: {
|
|
21
|
+
input: 'data-cnstrc-search-input',
|
|
22
|
+
inputForm: 'data-cnstrc-search-form',
|
|
23
|
+
searchSubmitButton: 'data-cnstrc-search-submit-btn',
|
|
24
|
+
autocompleteContainer: 'data-cnstrc-autosuggest',
|
|
25
|
+
},
|
|
26
|
+
recommendations: {
|
|
27
|
+
recommendationsContainer: 'data-cnstrc-recommendations',
|
|
28
|
+
recommendationsPodId: 'data-cnstrc-recommendations-pod-id',
|
|
29
|
+
item: 'data-cnstrc-item',
|
|
30
|
+
strategyId: 'data-cnstrc-strategy-id',
|
|
31
|
+
seedItems: 'data-cnstrc-recommendations-seed-items',
|
|
32
|
+
},
|
|
33
|
+
custom: {
|
|
34
|
+
customSection: 'data-cnstrc-custom-section',
|
|
35
|
+
customSectionName: 'data-cnstrc-custom-section-name',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Normalize itemIds to array format for seed items
|
|
40
|
+
*/
|
|
41
|
+
function normalizeSeedItems(itemIds) {
|
|
42
|
+
if (!itemIds)
|
|
43
|
+
return undefined;
|
|
44
|
+
return Array.isArray(itemIds) ? itemIds : [itemIds];
|
|
45
|
+
}
|
|
46
|
+
exports.normalizeSeedItems = normalizeSeedItems;
|
|
47
|
+
/**
|
|
48
|
+
* Get data attributes for autocomplete/recommendation items
|
|
49
|
+
*
|
|
50
|
+
* @param item - The item to generate data attributes for
|
|
51
|
+
* @returns An object containing data attributes to be spread onto the element
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* // For autocomplete items
|
|
56
|
+
* const itemProps = getItemCnstrcDataAttributes(searchSuggestionItem);
|
|
57
|
+
* <li {...itemProps}>Search Suggestion</li>
|
|
58
|
+
*
|
|
59
|
+
* // For recommendation items
|
|
60
|
+
* const recItemProps = getItemCnstrcDataAttributes(recommendationItem);
|
|
61
|
+
* <div {...recItemProps}>Recommended Product</div>
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
function getItemCnstrcDataAttributes(item) {
|
|
65
|
+
var _a, _b, _c, _d;
|
|
66
|
+
const dataCnstrc = {
|
|
67
|
+
[exports.cnstrcDataAttrs.common.itemSection]: item.section,
|
|
68
|
+
[exports.cnstrcDataAttrs.common.itemName]: item.value,
|
|
69
|
+
};
|
|
70
|
+
// Add item ID only for non-Search Suggestions
|
|
71
|
+
if (!(0, typeGuards_1.isSearchSuggestion)(item) && ((_a = item.data) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
72
|
+
dataCnstrc[exports.cnstrcDataAttrs.common.itemId] = item.data.id;
|
|
73
|
+
}
|
|
74
|
+
// Add variation ID if exists
|
|
75
|
+
if ((_b = item.data) === null || _b === void 0 ? void 0 : _b.variation_id) {
|
|
76
|
+
dataCnstrc[exports.cnstrcDataAttrs.common.variationId] = item.data.variation_id;
|
|
77
|
+
}
|
|
78
|
+
// Add group ID for in-group suggestions
|
|
79
|
+
if ((0, typeGuards_1.isInGroupSuggestion)(item)) {
|
|
80
|
+
dataCnstrc[exports.cnstrcDataAttrs.common.itemGroup] = item.groupId;
|
|
81
|
+
}
|
|
82
|
+
// Add recommendation-specific attributes (check if item has podId and strategy)
|
|
83
|
+
if (item.podId && item.strategy) {
|
|
84
|
+
dataCnstrc[exports.cnstrcDataAttrs.recommendations.item] = 'recommendation';
|
|
85
|
+
// Add strategy ID if available
|
|
86
|
+
if (item.strategy.id) {
|
|
87
|
+
dataCnstrc[exports.cnstrcDataAttrs.recommendations.strategyId] = item.strategy.id;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Add sponsored listing data if available
|
|
91
|
+
if ((_c = item.labels) === null || _c === void 0 ? void 0 : _c.sl_campaign_id) {
|
|
92
|
+
dataCnstrc[exports.cnstrcDataAttrs.common.slCampaignId] = String(item.labels.sl_campaign_id);
|
|
93
|
+
}
|
|
94
|
+
if ((_d = item.labels) === null || _d === void 0 ? void 0 : _d.sl_campaign_owner) {
|
|
95
|
+
dataCnstrc[exports.cnstrcDataAttrs.common.slCampaignOwner] = String(item.labels.sl_campaign_owner);
|
|
96
|
+
}
|
|
97
|
+
return dataCnstrc;
|
|
98
|
+
}
|
|
99
|
+
exports.getItemCnstrcDataAttributes = getItemCnstrcDataAttributes;
|
|
100
|
+
/**
|
|
101
|
+
* Get data attributes for recommendation sections
|
|
102
|
+
*/
|
|
103
|
+
function getRecommendationsSectionCnstrcDataAttributes(section, resultId, numResults, seedItems) {
|
|
104
|
+
if (!(0, typeGuards_1.isRecommendationsSection)(section)) {
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
const dataCnstrc = {
|
|
108
|
+
[exports.cnstrcDataAttrs.recommendations.recommendationsContainer]: true,
|
|
109
|
+
[exports.cnstrcDataAttrs.recommendations.recommendationsPodId]: section.podId,
|
|
110
|
+
};
|
|
111
|
+
// Add result ID if available
|
|
112
|
+
if (resultId) {
|
|
113
|
+
dataCnstrc[exports.cnstrcDataAttrs.common.resultId] = resultId;
|
|
114
|
+
}
|
|
115
|
+
// Add number of results
|
|
116
|
+
if (numResults !== undefined) {
|
|
117
|
+
dataCnstrc[exports.cnstrcDataAttrs.common.numResults] = numResults;
|
|
118
|
+
}
|
|
119
|
+
// Add seed items if available
|
|
120
|
+
if (seedItems && seedItems.length > 0) {
|
|
121
|
+
dataCnstrc[exports.cnstrcDataAttrs.recommendations.seedItems] = seedItems.join(',');
|
|
122
|
+
}
|
|
123
|
+
return dataCnstrc;
|
|
124
|
+
}
|
|
125
|
+
exports.getRecommendationsSectionCnstrcDataAttributes = getRecommendationsSectionCnstrcDataAttributes;
|
|
126
|
+
/**
|
|
127
|
+
* Get data attributes for conversion buttons
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```tsx
|
|
131
|
+
* <button {...getConversionButtonCnstrcDataAttributes('add_to_cart')}>
|
|
132
|
+
* Add to Cart
|
|
133
|
+
* </button>
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
function getConversionButtonCnstrcDataAttributes(conversionType) {
|
|
137
|
+
return {
|
|
138
|
+
[exports.cnstrcDataAttrs.common.conversionBtn]: conversionType,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
exports.getConversionButtonCnstrcDataAttributes = getConversionButtonCnstrcDataAttributes;
|
package/lib/cjs/version.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
2
|
import { CioAutocompleteContext } from '../CioAutocompleteProvider';
|
|
3
|
+
import { cnstrcDataAttrs } from '../../../utils/dataAttributeHelpers';
|
|
3
4
|
function DefaultRenderInput({ getFormProps, getInputProps, getLabelProps, setQuery }) {
|
|
4
5
|
const inputProps = getInputProps();
|
|
5
6
|
return (React.createElement("form", { ...getFormProps() },
|
|
@@ -14,7 +15,7 @@ function DefaultRenderInput({ getFormProps, getInputProps, getLabelProps, setQue
|
|
|
14
15
|
React.createElement("div", { className: 'cio-icon' },
|
|
15
16
|
React.createElement("svg", { stroke: 'currentColor', fill: 'currentColor', strokeWidth: '0', viewBox: '0 0 512 512', height: '1em', width: '1em', xmlns: 'http://www.w3.org/2000/svg' },
|
|
16
17
|
React.createElement("path", { d: 'M289.94 256l95-95A24 24 0 00351 127l-95 95-95-95a24 24 0 00-34 34l95 95-95 95a24 24 0 1034 34l95-95 95 95a24 24 0 0034-34z' })))),
|
|
17
|
-
React.createElement("button", { className: 'cio-submit-btn', "data-testid": 'cio-submit-btn',
|
|
18
|
+
React.createElement("button", { className: 'cio-submit-btn', "data-testid": 'cio-submit-btn', ...{ [cnstrcDataAttrs.autocomplete.searchSubmitButton]: '' }, onClick: (e) => {
|
|
18
19
|
if (!inputProps.value) {
|
|
19
20
|
e.preventDefault();
|
|
20
21
|
if (inputProps.id) {
|
|
@@ -5,6 +5,7 @@ import { translate } from '../../../utils/helpers';
|
|
|
5
5
|
import { camelToStartCase } from '../../../utils/format';
|
|
6
6
|
import { CioAutocompleteContext } from '../CioAutocompleteProvider';
|
|
7
7
|
import NoResults from '../AutocompleteResults/NoResults';
|
|
8
|
+
import { cnstrcDataAttrs } from '../../../utils/dataAttributeHelpers';
|
|
8
9
|
// eslint-disable-next-line func-names
|
|
9
10
|
const DefaultRenderSectionItemsList = function ({ section }) {
|
|
10
11
|
const { getSectionProps, query, getFormProps, advancedParameters } = useContext(CioAutocompleteContext);
|
|
@@ -47,7 +48,7 @@ const DefaultRenderSectionItemsList = function ({ section }) {
|
|
|
47
48
|
displayShowAllResultsButton &&
|
|
48
49
|
(typeof type === 'undefined' || type === 'autocomplete') &&
|
|
49
50
|
section.indexSectionName === 'Products' && (React.createElement("div", { className: 'cio-section-footer' },
|
|
50
|
-
React.createElement("button", {
|
|
51
|
+
React.createElement("button", { ...{ [cnstrcDataAttrs.autocomplete.searchSubmitButton]: '' }, className: 'cio-show-all-results-button', type: 'button', onClick: onSubmit }, translate('show all results', translations))))));
|
|
51
52
|
};
|
|
52
53
|
export default function SectionItemsList(props) {
|
|
53
54
|
const { section, children = DefaultRenderSectionItemsList } = props;
|
|
@@ -13,6 +13,7 @@ import useRecommendationsObserver from './useRecommendationsObserver';
|
|
|
13
13
|
import { isCustomSection, isRecommendationsSection } from '../typeGuards';
|
|
14
14
|
import useNormalizedProps from './useNormalizedProps';
|
|
15
15
|
import useCustomBlur from './useCustomBlur';
|
|
16
|
+
import { getItemCnstrcDataAttributes, getRecommendationsSectionCnstrcDataAttributes, cnstrcDataAttrs, normalizeSeedItems, } from '../utils/dataAttributeHelpers';
|
|
16
17
|
import { shopifyDefaults } from '../utils/shopifyDefaults';
|
|
17
18
|
export const defaultSections = [
|
|
18
19
|
{
|
|
@@ -50,7 +51,7 @@ const useCioAutocomplete = (options) => {
|
|
|
50
51
|
cioJsClientOptions: options.cioJsClientOptions,
|
|
51
52
|
});
|
|
52
53
|
// Get autocomplete sections (autocomplete + recommendations + custom)
|
|
53
|
-
const { fetchRecommendationResults, activeSections, activeSectionsWithData, zeroStateActiveSections, request, totalNumResultsPerSection, } = useSections(query, cioClient, sections, zeroStateSections, advancedParameters);
|
|
54
|
+
const { fetchRecommendationResults, activeSections, activeSectionsWithData, zeroStateActiveSections, request, totalNumResultsPerSection, podsData, } = useSections(query, cioClient, sections, zeroStateSections, advancedParameters);
|
|
54
55
|
const features = useMemo(() => getFeatures(request), [request]);
|
|
55
56
|
// Get dropdown items array from active sections (autocomplete + recommendations + custom)
|
|
56
57
|
const items = useMemo(() => getItemsForActiveSections(activeSectionsWithData), [activeSectionsWithData]);
|
|
@@ -78,7 +79,7 @@ const useCioAutocomplete = (options) => {
|
|
|
78
79
|
...getMenuProps(),
|
|
79
80
|
className: 'cio-results',
|
|
80
81
|
'data-testid': 'cio-results',
|
|
81
|
-
|
|
82
|
+
[cnstrcDataAttrs.autocomplete.autocompleteContainer]: '',
|
|
82
83
|
}),
|
|
83
84
|
getLabelProps,
|
|
84
85
|
openMenu,
|
|
@@ -100,17 +101,14 @@ const useCioAutocomplete = (options) => {
|
|
|
100
101
|
}
|
|
101
102
|
},
|
|
102
103
|
};
|
|
104
|
+
// Get all data attributes using the helper function
|
|
105
|
+
const dataAttributes = getItemCnstrcDataAttributes(item);
|
|
103
106
|
return {
|
|
104
107
|
...getItemPropsDownShift({ item, index }),
|
|
105
108
|
// @deprecated `sectionItemTestId` will be removed as a className in the next major version
|
|
106
109
|
className: `cio-item ${sectionItemTestId}`,
|
|
107
110
|
'data-testid': sectionItemTestId,
|
|
108
|
-
|
|
109
|
-
'data-cnstrc-item-group': item.groupId,
|
|
110
|
-
'data-cnstrc-item-name': item.value,
|
|
111
|
-
'data-cnstrc-item-id': item.data?.id,
|
|
112
|
-
'data-cnstrc-sl-campaign-id': item.labels?.sl_campaign_id,
|
|
113
|
-
'data-cnstrc-sl-campaign-owner': item.labels?.sl_campaign_owner,
|
|
111
|
+
...dataAttributes,
|
|
114
112
|
...(hasLink ? {} : nonInteractiveItemsProps),
|
|
115
113
|
};
|
|
116
114
|
},
|
|
@@ -149,7 +147,7 @@ const useCioAutocomplete = (options) => {
|
|
|
149
147
|
},
|
|
150
148
|
className: 'cio-input',
|
|
151
149
|
'data-testid': 'cio-input',
|
|
152
|
-
|
|
150
|
+
[cnstrcDataAttrs.autocomplete.input]: '',
|
|
153
151
|
placeholder,
|
|
154
152
|
onKeyDownCapture: ({ code, key, nativeEvent }) => {
|
|
155
153
|
const isEnter = code === 'Enter' || key === 'Enter';
|
|
@@ -191,7 +189,7 @@ const useCioAutocomplete = (options) => {
|
|
|
191
189
|
},
|
|
192
190
|
className: 'cio-form',
|
|
193
191
|
'data-testid': 'cio-form',
|
|
194
|
-
|
|
192
|
+
[cnstrcDataAttrs.autocomplete.inputForm]: '',
|
|
195
193
|
}),
|
|
196
194
|
getSectionProps: (section) => {
|
|
197
195
|
// @deprecated ClassNames derived from this fn will be removed in the next major version
|
|
@@ -227,16 +225,18 @@ const useCioAutocomplete = (options) => {
|
|
|
227
225
|
className: `cio-section cio-section-${sectionListingType} ${getDeprecatedClassNames()}`,
|
|
228
226
|
ref: section.ref,
|
|
229
227
|
role: 'none',
|
|
230
|
-
|
|
228
|
+
[cnstrcDataAttrs.common.section]: section.data[0]?.section,
|
|
231
229
|
};
|
|
232
230
|
if (isCustomSection(section)) {
|
|
233
|
-
attributes[
|
|
234
|
-
attributes[
|
|
231
|
+
attributes[cnstrcDataAttrs.custom.customSection] = true;
|
|
232
|
+
attributes[cnstrcDataAttrs.custom.customSectionName] = section.displayName;
|
|
235
233
|
}
|
|
236
|
-
// Add data attributes for recommendations
|
|
234
|
+
// Add data attributes for recommendations using helper
|
|
237
235
|
if (isRecommendationsSection(section)) {
|
|
238
|
-
|
|
239
|
-
|
|
236
|
+
const podData = podsData?.[section.podId];
|
|
237
|
+
const seedItems = normalizeSeedItems(section.itemIds);
|
|
238
|
+
const recommendationAttributes = getRecommendationsSectionCnstrcDataAttributes(section, podData?.resultId, podData?.numResults, seedItems);
|
|
239
|
+
Object.assign(attributes, recommendationAttributes);
|
|
240
240
|
}
|
|
241
241
|
return attributes;
|
|
242
242
|
},
|
|
@@ -11,7 +11,7 @@ const useFetchRecommendationPod = (cioClient, recommendationPods, fetchZeroState
|
|
|
11
11
|
})));
|
|
12
12
|
const recommendationsPodResults = {};
|
|
13
13
|
const recommendationsPodsData = {};
|
|
14
|
-
responses.forEach(({ response, request }, index) => {
|
|
14
|
+
responses.forEach(({ response, request, result_id: resultId }, index) => {
|
|
15
15
|
const { pod, results } = response;
|
|
16
16
|
if (pod?.id) {
|
|
17
17
|
recommendationsPodResults[pod.id] = results?.map((item) => ({
|
|
@@ -24,6 +24,8 @@ const useFetchRecommendationPod = (cioClient, recommendationPods, fetchZeroState
|
|
|
24
24
|
displayName: pod.display_name,
|
|
25
25
|
podId: pod.id,
|
|
26
26
|
request,
|
|
27
|
+
resultId,
|
|
28
|
+
numResults: response.total_num_results ?? 0,
|
|
27
29
|
};
|
|
28
30
|
}
|
|
29
31
|
});
|
|
@@ -30,5 +30,6 @@ export default function useSections(query, cioClient, sections, zeroStateSection
|
|
|
30
30
|
zeroStateActiveSections: showZeroStateSections,
|
|
31
31
|
request: autocomplete.request,
|
|
32
32
|
totalNumResultsPerSection: autocomplete.totalNumResultsPerSection,
|
|
33
|
+
podsData: recommendations.podsData,
|
|
33
34
|
};
|
|
34
35
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { isInGroupSuggestion, isRecommendationsSection, isSearchSuggestion } from '../typeGuards';
|
|
2
|
+
export const cnstrcDataAttrs = {
|
|
3
|
+
common: {
|
|
4
|
+
itemId: 'data-cnstrc-item-id',
|
|
5
|
+
itemName: 'data-cnstrc-item-name',
|
|
6
|
+
itemPrice: 'data-cnstrc-item-price',
|
|
7
|
+
variationId: 'data-cnstrc-item-variation-id',
|
|
8
|
+
numResults: 'data-cnstrc-num-results',
|
|
9
|
+
conversionBtn: 'data-cnstrc-btn',
|
|
10
|
+
resultId: 'data-cnstrc-result-id',
|
|
11
|
+
itemSection: 'data-cnstrc-item-section',
|
|
12
|
+
itemGroup: 'data-cnstrc-item-group',
|
|
13
|
+
slCampaignId: 'data-cnstrc-sl-campaign-id',
|
|
14
|
+
slCampaignOwner: 'data-cnstrc-sl-campaign-owner',
|
|
15
|
+
section: 'data-cnstrc-section',
|
|
16
|
+
},
|
|
17
|
+
autocomplete: {
|
|
18
|
+
input: 'data-cnstrc-search-input',
|
|
19
|
+
inputForm: 'data-cnstrc-search-form',
|
|
20
|
+
searchSubmitButton: 'data-cnstrc-search-submit-btn',
|
|
21
|
+
autocompleteContainer: 'data-cnstrc-autosuggest',
|
|
22
|
+
},
|
|
23
|
+
recommendations: {
|
|
24
|
+
recommendationsContainer: 'data-cnstrc-recommendations',
|
|
25
|
+
recommendationsPodId: 'data-cnstrc-recommendations-pod-id',
|
|
26
|
+
item: 'data-cnstrc-item',
|
|
27
|
+
strategyId: 'data-cnstrc-strategy-id',
|
|
28
|
+
seedItems: 'data-cnstrc-recommendations-seed-items',
|
|
29
|
+
},
|
|
30
|
+
custom: {
|
|
31
|
+
customSection: 'data-cnstrc-custom-section',
|
|
32
|
+
customSectionName: 'data-cnstrc-custom-section-name',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Normalize itemIds to array format for seed items
|
|
37
|
+
*/
|
|
38
|
+
export function normalizeSeedItems(itemIds) {
|
|
39
|
+
if (!itemIds)
|
|
40
|
+
return undefined;
|
|
41
|
+
return Array.isArray(itemIds) ? itemIds : [itemIds];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get data attributes for autocomplete/recommendation items
|
|
45
|
+
*
|
|
46
|
+
* @param item - The item to generate data attributes for
|
|
47
|
+
* @returns An object containing data attributes to be spread onto the element
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```tsx
|
|
51
|
+
* // For autocomplete items
|
|
52
|
+
* const itemProps = getItemCnstrcDataAttributes(searchSuggestionItem);
|
|
53
|
+
* <li {...itemProps}>Search Suggestion</li>
|
|
54
|
+
*
|
|
55
|
+
* // For recommendation items
|
|
56
|
+
* const recItemProps = getItemCnstrcDataAttributes(recommendationItem);
|
|
57
|
+
* <div {...recItemProps}>Recommended Product</div>
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function getItemCnstrcDataAttributes(item) {
|
|
61
|
+
const dataCnstrc = {
|
|
62
|
+
[cnstrcDataAttrs.common.itemSection]: item.section,
|
|
63
|
+
[cnstrcDataAttrs.common.itemName]: item.value,
|
|
64
|
+
};
|
|
65
|
+
// Add item ID only for non-Search Suggestions
|
|
66
|
+
if (!isSearchSuggestion(item) && item.data?.id) {
|
|
67
|
+
dataCnstrc[cnstrcDataAttrs.common.itemId] = item.data.id;
|
|
68
|
+
}
|
|
69
|
+
// Add variation ID if exists
|
|
70
|
+
if (item.data?.variation_id) {
|
|
71
|
+
dataCnstrc[cnstrcDataAttrs.common.variationId] = item.data.variation_id;
|
|
72
|
+
}
|
|
73
|
+
// Add group ID for in-group suggestions
|
|
74
|
+
if (isInGroupSuggestion(item)) {
|
|
75
|
+
dataCnstrc[cnstrcDataAttrs.common.itemGroup] = item.groupId;
|
|
76
|
+
}
|
|
77
|
+
// Add recommendation-specific attributes (check if item has podId and strategy)
|
|
78
|
+
if (item.podId && item.strategy) {
|
|
79
|
+
dataCnstrc[cnstrcDataAttrs.recommendations.item] = 'recommendation';
|
|
80
|
+
// Add strategy ID if available
|
|
81
|
+
if (item.strategy.id) {
|
|
82
|
+
dataCnstrc[cnstrcDataAttrs.recommendations.strategyId] = item.strategy.id;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Add sponsored listing data if available
|
|
86
|
+
if (item.labels?.sl_campaign_id) {
|
|
87
|
+
dataCnstrc[cnstrcDataAttrs.common.slCampaignId] = String(item.labels.sl_campaign_id);
|
|
88
|
+
}
|
|
89
|
+
if (item.labels?.sl_campaign_owner) {
|
|
90
|
+
dataCnstrc[cnstrcDataAttrs.common.slCampaignOwner] = String(item.labels.sl_campaign_owner);
|
|
91
|
+
}
|
|
92
|
+
return dataCnstrc;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get data attributes for recommendation sections
|
|
96
|
+
*/
|
|
97
|
+
export function getRecommendationsSectionCnstrcDataAttributes(section, resultId, numResults, seedItems) {
|
|
98
|
+
if (!isRecommendationsSection(section)) {
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
const dataCnstrc = {
|
|
102
|
+
[cnstrcDataAttrs.recommendations.recommendationsContainer]: true,
|
|
103
|
+
[cnstrcDataAttrs.recommendations.recommendationsPodId]: section.podId,
|
|
104
|
+
};
|
|
105
|
+
// Add result ID if available
|
|
106
|
+
if (resultId) {
|
|
107
|
+
dataCnstrc[cnstrcDataAttrs.common.resultId] = resultId;
|
|
108
|
+
}
|
|
109
|
+
// Add number of results
|
|
110
|
+
if (numResults !== undefined) {
|
|
111
|
+
dataCnstrc[cnstrcDataAttrs.common.numResults] = numResults;
|
|
112
|
+
}
|
|
113
|
+
// Add seed items if available
|
|
114
|
+
if (seedItems && seedItems.length > 0) {
|
|
115
|
+
dataCnstrc[cnstrcDataAttrs.recommendations.seedItems] = seedItems.join(',');
|
|
116
|
+
}
|
|
117
|
+
return dataCnstrc;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get data attributes for conversion buttons
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```tsx
|
|
124
|
+
* <button {...getConversionButtonCnstrcDataAttributes('add_to_cart')}>
|
|
125
|
+
* Add to Cart
|
|
126
|
+
* </button>
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export function getConversionButtonCnstrcDataAttributes(conversionType) {
|
|
130
|
+
return {
|
|
131
|
+
[cnstrcDataAttrs.common.conversionBtn]: conversionType,
|
|
132
|
+
};
|
|
133
|
+
}
|
package/lib/mjs/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '1.
|
|
1
|
+
export default '1.29.0';
|
|
@@ -18,12 +18,14 @@ export declare const CioAutocompleteContext: React.Context<{
|
|
|
18
18
|
getItemProps: (item: import("../../types").Item) => any;
|
|
19
19
|
getInputProps: () => any;
|
|
20
20
|
getFormProps: () => {
|
|
21
|
+
[x: string]: string | ((event: any) => {
|
|
22
|
+
query: string;
|
|
23
|
+
});
|
|
21
24
|
onSubmit: (event: any) => {
|
|
22
25
|
query: string;
|
|
23
26
|
};
|
|
24
27
|
className: string;
|
|
25
28
|
'data-testid': string;
|
|
26
|
-
'data-cnstrc-search-form': string;
|
|
27
29
|
};
|
|
28
30
|
getSectionProps: (section: import("../../types").Section) => import("../../types").HTMLPropsWithCioDataAttributes<any>;
|
|
29
31
|
setQuery: React.Dispatch<React.SetStateAction<string>>;
|
|
@@ -18,12 +18,14 @@ declare const useCioAutocomplete: (options: UseCioAutocompleteOptions) => {
|
|
|
18
18
|
getItemProps: (item: Item) => any;
|
|
19
19
|
getInputProps: () => any;
|
|
20
20
|
getFormProps: () => {
|
|
21
|
+
[x: string]: string | ((event: any) => {
|
|
22
|
+
query: string;
|
|
23
|
+
});
|
|
21
24
|
onSubmit: (event: any) => {
|
|
22
25
|
query: string;
|
|
23
26
|
};
|
|
24
27
|
className: string;
|
|
25
28
|
'data-testid': string;
|
|
26
|
-
'data-cnstrc-search-form': string;
|
|
27
29
|
};
|
|
28
30
|
getSectionProps: (section: Section) => HTMLPropsWithCioDataAttributes<any>;
|
|
29
31
|
setQuery: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ConstructorIO from '@constructor-io/constructorio-client-javascript';
|
|
2
2
|
import { Nullable } from '@constructor-io/constructorio-client-javascript/lib/types';
|
|
3
|
-
import { AdvancedParameters, UserDefinedSection } from '../../types';
|
|
3
|
+
import { AdvancedParameters, PodData, UserDefinedSection } from '../../types';
|
|
4
4
|
export default function useSections(query: string, cioClient: Nullable<ConstructorIO>, sections: UserDefinedSection[], zeroStateSections?: UserDefinedSection[], advancedParameters?: AdvancedParameters): {
|
|
5
5
|
fetchRecommendationResults: () => Promise<void>;
|
|
6
6
|
activeSections: UserDefinedSection[];
|
|
@@ -8,4 +8,5 @@ export default function useSections(query: string, cioClient: Nullable<Construct
|
|
|
8
8
|
zeroStateActiveSections: boolean;
|
|
9
9
|
request: Partial<import("@constructor-io/constructorio-client-javascript").AutocompleteRequestType>;
|
|
10
10
|
totalNumResultsPerSection: Record<string, number>;
|
|
11
|
+
podsData: Record<string, PodData>;
|
|
11
12
|
};
|