@blotoutio/providers-evo-search-sdk 1.68.2 → 1.69.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/core.cjs.js +2 -2
- package/core.js +2 -2
- package/core.mjs +2 -2
- package/hooks.cjs.js +67 -4
- package/hooks.mjs +67 -4
- package/index.cjs.js +40 -10
- package/index.js +40 -10
- package/index.mjs +40 -10
- package/package.json +1 -1
package/core.cjs.js
CHANGED
|
@@ -1481,7 +1481,7 @@ const init = (params) => {
|
|
|
1481
1481
|
const tag = ({ data }) => {
|
|
1482
1482
|
try {
|
|
1483
1483
|
return {
|
|
1484
|
-
sdkVersion: "1.
|
|
1484
|
+
sdkVersion: "1.69.0" || '',
|
|
1485
1485
|
searchQuery: data['searchQuery'],
|
|
1486
1486
|
searchResults: data['searchResults'],
|
|
1487
1487
|
timestamp: Date.now(),
|
|
@@ -1490,7 +1490,7 @@ const tag = ({ data }) => {
|
|
|
1490
1490
|
catch (err) {
|
|
1491
1491
|
console.error(`EvoSearch SDK: Tagging failed - ${err}`);
|
|
1492
1492
|
return {
|
|
1493
|
-
sdkVersion: "1.
|
|
1493
|
+
sdkVersion: "1.69.0" ,
|
|
1494
1494
|
timestamp: Date.now(),
|
|
1495
1495
|
};
|
|
1496
1496
|
}
|
package/core.js
CHANGED
|
@@ -1482,7 +1482,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
1482
1482
|
const tag = ({ data }) => {
|
|
1483
1483
|
try {
|
|
1484
1484
|
return {
|
|
1485
|
-
sdkVersion: "1.
|
|
1485
|
+
sdkVersion: "1.69.0" || '',
|
|
1486
1486
|
searchQuery: data['searchQuery'],
|
|
1487
1487
|
searchResults: data['searchResults'],
|
|
1488
1488
|
timestamp: Date.now(),
|
|
@@ -1491,7 +1491,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
1491
1491
|
catch (err) {
|
|
1492
1492
|
console.error(`EvoSearch SDK: Tagging failed - ${err}`);
|
|
1493
1493
|
return {
|
|
1494
|
-
sdkVersion: "1.
|
|
1494
|
+
sdkVersion: "1.69.0" ,
|
|
1495
1495
|
timestamp: Date.now(),
|
|
1496
1496
|
};
|
|
1497
1497
|
}
|
package/core.mjs
CHANGED
|
@@ -1479,7 +1479,7 @@ const init = (params) => {
|
|
|
1479
1479
|
const tag = ({ data }) => {
|
|
1480
1480
|
try {
|
|
1481
1481
|
return {
|
|
1482
|
-
sdkVersion: "1.
|
|
1482
|
+
sdkVersion: "1.69.0" || '',
|
|
1483
1483
|
searchQuery: data['searchQuery'],
|
|
1484
1484
|
searchResults: data['searchResults'],
|
|
1485
1485
|
timestamp: Date.now(),
|
|
@@ -1488,7 +1488,7 @@ const tag = ({ data }) => {
|
|
|
1488
1488
|
catch (err) {
|
|
1489
1489
|
console.error(`EvoSearch SDK: Tagging failed - ${err}`);
|
|
1490
1490
|
return {
|
|
1491
|
-
sdkVersion: "1.
|
|
1491
|
+
sdkVersion: "1.69.0" ,
|
|
1492
1492
|
timestamp: Date.now(),
|
|
1493
1493
|
};
|
|
1494
1494
|
}
|
package/hooks.cjs.js
CHANGED
|
@@ -935,7 +935,19 @@ const useEvoSearch = (options = {}) => {
|
|
|
935
935
|
}, [query, ready, api, mode, retryNonce]);
|
|
936
936
|
// Widget parity: report a settled query as one real search after SEARCH_SETTLE_MS.
|
|
937
937
|
const lastFinalizedRef = react.useRef('');
|
|
938
|
+
// The query whose rendered results are waiting out the settle pause. Held in
|
|
939
|
+
// a ref so the teardown flush below can reach it: on a headless storefront a
|
|
940
|
+
// product click is a full navigation, so a shopper who clicks within the
|
|
941
|
+
// settle window unmounts this hook before the timer fires — and losing that
|
|
942
|
+
// event deletes exactly the successful searches from the finalized count.
|
|
943
|
+
// Measured on a real store at ~2x scale (zox, solutions#9987).
|
|
944
|
+
const pendingFinalizeRef = react.useRef(null);
|
|
938
945
|
react.useEffect(() => {
|
|
946
|
+
// Re-evaluated on every dependency change, so anything short of an armed
|
|
947
|
+
// timer means nothing is pending: a superseded query's results left the
|
|
948
|
+
// screen with it, and flushing them from teardown would count a search the
|
|
949
|
+
// shopper abandoned.
|
|
950
|
+
pendingFinalizeRef.current = null;
|
|
939
951
|
if (!api || mode === 'autocomplete' || loading || error) {
|
|
940
952
|
return;
|
|
941
953
|
}
|
|
@@ -948,16 +960,51 @@ const useEvoSearch = (options = {}) => {
|
|
|
948
960
|
if (cleaned.toLowerCase() === lastFinalizedRef.current) {
|
|
949
961
|
return;
|
|
950
962
|
}
|
|
963
|
+
const actionData = {
|
|
964
|
+
query: cleaned,
|
|
965
|
+
resultCount: results.length,
|
|
966
|
+
};
|
|
967
|
+
pendingFinalizeRef.current = { api, actionData };
|
|
951
968
|
const timer = setTimeout(() => {
|
|
969
|
+
pendingFinalizeRef.current = null;
|
|
970
|
+
// Re-checked at fire time, not only at arm time: a pagehide flush may
|
|
971
|
+
// have counted this query while the timer was pending (a bfcache return
|
|
972
|
+
// resumes it), and firing again would double the search.
|
|
973
|
+
if (cleaned.toLowerCase() === lastFinalizedRef.current) {
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
952
976
|
lastFinalizedRef.current = cleaned.toLowerCase();
|
|
953
|
-
const actionData = {
|
|
954
|
-
query: cleaned,
|
|
955
|
-
resultCount: results.length,
|
|
956
|
-
};
|
|
957
977
|
api.sendEvent('evoSearchSearchIssued', undefined, actionData);
|
|
958
978
|
}, SEARCH_SETTLE_MS);
|
|
979
|
+
// Only the timer: the pending marker survives this cleanup so a teardown
|
|
980
|
+
// that never re-runs the effect (unmount, navigation) can still flush it.
|
|
981
|
+
// A dependency change that IS a re-run nulls it at the top first.
|
|
959
982
|
return () => clearTimeout(timer);
|
|
960
983
|
}, [api, mode, query, results, loading, error]);
|
|
984
|
+
// The teardown flush. A shopper who clicks a result inside the settle window
|
|
985
|
+
// performed the most successful search there is; `sendEvent` posts with
|
|
986
|
+
// keepalive, so firing it from pagehide/unmount survives the navigation.
|
|
987
|
+
// Marking `lastFinalizedRef` keeps a bfcache return from settling the same
|
|
988
|
+
// query into a second event.
|
|
989
|
+
react.useEffect(() => {
|
|
990
|
+
if (typeof window === 'undefined') {
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
const flush = () => {
|
|
994
|
+
const pending = pendingFinalizeRef.current;
|
|
995
|
+
if (!pending) {
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
pendingFinalizeRef.current = null;
|
|
999
|
+
lastFinalizedRef.current = pending.actionData.query.trim().toLowerCase();
|
|
1000
|
+
pending.api.sendEvent('evoSearchSearchIssued', undefined, pending.actionData);
|
|
1001
|
+
};
|
|
1002
|
+
window.addEventListener('pagehide', flush);
|
|
1003
|
+
return () => {
|
|
1004
|
+
window.removeEventListener('pagehide', flush);
|
|
1005
|
+
flush();
|
|
1006
|
+
};
|
|
1007
|
+
}, []);
|
|
961
1008
|
// Image (or image+text) search, imperative because it is driven by a file
|
|
962
1009
|
// picker rather than the query state. Deliberately NOT given the retrying
|
|
963
1010
|
// resilience defaults: each attempt costs a multimodal embed, so we take the
|
|
@@ -982,6 +1029,22 @@ const useEvoSearch = (options = {}) => {
|
|
|
982
1029
|
setResults(res.products.map(mapAPIProductToSDKProduct));
|
|
983
1030
|
// Suggestions are a text-search artifact; an image query has none.
|
|
984
1031
|
setSuggestions([]);
|
|
1032
|
+
// A typed refinement over a photo is a query with text, and the server
|
|
1033
|
+
// counts it off this event exactly as it counts a settled keyword
|
|
1034
|
+
// search — nothing else counts it, because an `image+text` request row
|
|
1035
|
+
// is neither a keyword row nor a per-action submit. Per action rather
|
|
1036
|
+
// than settled: this is an imperative submit, so there is no typing
|
|
1037
|
+
// pause to wait out and no `query` state for the settle effect to see.
|
|
1038
|
+
// A pure image search is counted server-side from its own request row
|
|
1039
|
+
// and must not be double-reported here.
|
|
1040
|
+
const refinement = text === null || text === void 0 ? void 0 : text.trim();
|
|
1041
|
+
if (refinement) {
|
|
1042
|
+
const imageActionData = {
|
|
1043
|
+
query: refinement,
|
|
1044
|
+
resultCount: res.products.length,
|
|
1045
|
+
};
|
|
1046
|
+
api.sendEvent('evoSearchSearchIssued', undefined, imageActionData);
|
|
1047
|
+
}
|
|
985
1048
|
}
|
|
986
1049
|
catch (err) {
|
|
987
1050
|
if (err instanceof DOMException && err.name === 'AbortError') {
|
package/hooks.mjs
CHANGED
|
@@ -933,7 +933,19 @@ const useEvoSearch = (options = {}) => {
|
|
|
933
933
|
}, [query, ready, api, mode, retryNonce]);
|
|
934
934
|
// Widget parity: report a settled query as one real search after SEARCH_SETTLE_MS.
|
|
935
935
|
const lastFinalizedRef = useRef('');
|
|
936
|
+
// The query whose rendered results are waiting out the settle pause. Held in
|
|
937
|
+
// a ref so the teardown flush below can reach it: on a headless storefront a
|
|
938
|
+
// product click is a full navigation, so a shopper who clicks within the
|
|
939
|
+
// settle window unmounts this hook before the timer fires — and losing that
|
|
940
|
+
// event deletes exactly the successful searches from the finalized count.
|
|
941
|
+
// Measured on a real store at ~2x scale (zox, solutions#9987).
|
|
942
|
+
const pendingFinalizeRef = useRef(null);
|
|
936
943
|
useEffect(() => {
|
|
944
|
+
// Re-evaluated on every dependency change, so anything short of an armed
|
|
945
|
+
// timer means nothing is pending: a superseded query's results left the
|
|
946
|
+
// screen with it, and flushing them from teardown would count a search the
|
|
947
|
+
// shopper abandoned.
|
|
948
|
+
pendingFinalizeRef.current = null;
|
|
937
949
|
if (!api || mode === 'autocomplete' || loading || error) {
|
|
938
950
|
return;
|
|
939
951
|
}
|
|
@@ -946,16 +958,51 @@ const useEvoSearch = (options = {}) => {
|
|
|
946
958
|
if (cleaned.toLowerCase() === lastFinalizedRef.current) {
|
|
947
959
|
return;
|
|
948
960
|
}
|
|
961
|
+
const actionData = {
|
|
962
|
+
query: cleaned,
|
|
963
|
+
resultCount: results.length,
|
|
964
|
+
};
|
|
965
|
+
pendingFinalizeRef.current = { api, actionData };
|
|
949
966
|
const timer = setTimeout(() => {
|
|
967
|
+
pendingFinalizeRef.current = null;
|
|
968
|
+
// Re-checked at fire time, not only at arm time: a pagehide flush may
|
|
969
|
+
// have counted this query while the timer was pending (a bfcache return
|
|
970
|
+
// resumes it), and firing again would double the search.
|
|
971
|
+
if (cleaned.toLowerCase() === lastFinalizedRef.current) {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
950
974
|
lastFinalizedRef.current = cleaned.toLowerCase();
|
|
951
|
-
const actionData = {
|
|
952
|
-
query: cleaned,
|
|
953
|
-
resultCount: results.length,
|
|
954
|
-
};
|
|
955
975
|
api.sendEvent('evoSearchSearchIssued', undefined, actionData);
|
|
956
976
|
}, SEARCH_SETTLE_MS);
|
|
977
|
+
// Only the timer: the pending marker survives this cleanup so a teardown
|
|
978
|
+
// that never re-runs the effect (unmount, navigation) can still flush it.
|
|
979
|
+
// A dependency change that IS a re-run nulls it at the top first.
|
|
957
980
|
return () => clearTimeout(timer);
|
|
958
981
|
}, [api, mode, query, results, loading, error]);
|
|
982
|
+
// The teardown flush. A shopper who clicks a result inside the settle window
|
|
983
|
+
// performed the most successful search there is; `sendEvent` posts with
|
|
984
|
+
// keepalive, so firing it from pagehide/unmount survives the navigation.
|
|
985
|
+
// Marking `lastFinalizedRef` keeps a bfcache return from settling the same
|
|
986
|
+
// query into a second event.
|
|
987
|
+
useEffect(() => {
|
|
988
|
+
if (typeof window === 'undefined') {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
const flush = () => {
|
|
992
|
+
const pending = pendingFinalizeRef.current;
|
|
993
|
+
if (!pending) {
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
pendingFinalizeRef.current = null;
|
|
997
|
+
lastFinalizedRef.current = pending.actionData.query.trim().toLowerCase();
|
|
998
|
+
pending.api.sendEvent('evoSearchSearchIssued', undefined, pending.actionData);
|
|
999
|
+
};
|
|
1000
|
+
window.addEventListener('pagehide', flush);
|
|
1001
|
+
return () => {
|
|
1002
|
+
window.removeEventListener('pagehide', flush);
|
|
1003
|
+
flush();
|
|
1004
|
+
};
|
|
1005
|
+
}, []);
|
|
959
1006
|
// Image (or image+text) search, imperative because it is driven by a file
|
|
960
1007
|
// picker rather than the query state. Deliberately NOT given the retrying
|
|
961
1008
|
// resilience defaults: each attempt costs a multimodal embed, so we take the
|
|
@@ -980,6 +1027,22 @@ const useEvoSearch = (options = {}) => {
|
|
|
980
1027
|
setResults(res.products.map(mapAPIProductToSDKProduct));
|
|
981
1028
|
// Suggestions are a text-search artifact; an image query has none.
|
|
982
1029
|
setSuggestions([]);
|
|
1030
|
+
// A typed refinement over a photo is a query with text, and the server
|
|
1031
|
+
// counts it off this event exactly as it counts a settled keyword
|
|
1032
|
+
// search — nothing else counts it, because an `image+text` request row
|
|
1033
|
+
// is neither a keyword row nor a per-action submit. Per action rather
|
|
1034
|
+
// than settled: this is an imperative submit, so there is no typing
|
|
1035
|
+
// pause to wait out and no `query` state for the settle effect to see.
|
|
1036
|
+
// A pure image search is counted server-side from its own request row
|
|
1037
|
+
// and must not be double-reported here.
|
|
1038
|
+
const refinement = text === null || text === void 0 ? void 0 : text.trim();
|
|
1039
|
+
if (refinement) {
|
|
1040
|
+
const imageActionData = {
|
|
1041
|
+
query: refinement,
|
|
1042
|
+
resultCount: res.products.length,
|
|
1043
|
+
};
|
|
1044
|
+
api.sendEvent('evoSearchSearchIssued', undefined, imageActionData);
|
|
1045
|
+
}
|
|
983
1046
|
}
|
|
984
1047
|
catch (err) {
|
|
985
1048
|
if (err instanceof DOMException && err.name === 'AbortError') {
|
package/index.cjs.js
CHANGED
|
@@ -2096,7 +2096,7 @@ const init = (params) => {
|
|
|
2096
2096
|
const tag = ({ data }) => {
|
|
2097
2097
|
try {
|
|
2098
2098
|
return {
|
|
2099
|
-
sdkVersion: "1.
|
|
2099
|
+
sdkVersion: "1.69.0" || '',
|
|
2100
2100
|
searchQuery: data['searchQuery'],
|
|
2101
2101
|
searchResults: data['searchResults'],
|
|
2102
2102
|
timestamp: Date.now(),
|
|
@@ -2105,7 +2105,7 @@ const tag = ({ data }) => {
|
|
|
2105
2105
|
catch (err) {
|
|
2106
2106
|
console.error(`EvoSearch SDK: Tagging failed - ${err}`);
|
|
2107
2107
|
return {
|
|
2108
|
-
sdkVersion: "1.
|
|
2108
|
+
sdkVersion: "1.69.0" ,
|
|
2109
2109
|
timestamp: Date.now(),
|
|
2110
2110
|
};
|
|
2111
2111
|
}
|
|
@@ -9870,6 +9870,8 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
9870
9870
|
};
|
|
9871
9871
|
this.isHandlingSuggestionClick = false;
|
|
9872
9872
|
this.cachedRailSections = [];
|
|
9873
|
+
// Bound once so connected/disconnected add and remove the SAME reference.
|
|
9874
|
+
this.finalizeFlushListener = () => this.flushSettledQuery();
|
|
9873
9875
|
// Last finalized query (lowercased) — consecutive duplicates (settle
|
|
9874
9876
|
// followed by Enter on the same query) collapse into one search.
|
|
9875
9877
|
this.lastFinalizedQuery = '';
|
|
@@ -10035,6 +10037,12 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10035
10037
|
this.mobileMediaQuery.addEventListener('change', this.mobileMediaListener);
|
|
10036
10038
|
window.addEventListener('online', this.rewarmListener);
|
|
10037
10039
|
document.addEventListener('visibilitychange', this.rewarmListener);
|
|
10040
|
+
// A product card is a plain anchor, so a click inside the settle window is
|
|
10041
|
+
// a full navigation that would otherwise kill the pending finalize — and
|
|
10042
|
+
// a fast click-through is the most successful search there is. sendEvent
|
|
10043
|
+
// posts with keepalive, so flushing from pagehide survives the unload;
|
|
10044
|
+
// markQueryFinalized's dedupe keeps a bfcache return from doubling it.
|
|
10045
|
+
window.addEventListener('pagehide', this.finalizeFlushListener);
|
|
10038
10046
|
// Restart keep-warm when the same instance re-attaches (a host re-append
|
|
10039
10047
|
// moves the subtree and fires disconnected→connected without a fresh
|
|
10040
10048
|
// firstUpdated). On the very first connect `api` may not be bound yet —
|
|
@@ -10072,8 +10080,11 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10072
10080
|
}
|
|
10073
10081
|
disconnectedCallback() {
|
|
10074
10082
|
var _a, _b, _c, _d, _e;
|
|
10075
|
-
// Closing the widget
|
|
10076
|
-
|
|
10083
|
+
// Closing the widget — and a host-driven teardown that removes the overlay
|
|
10084
|
+
// mid-settle — settles whatever query was waiting out its pause, the same
|
|
10085
|
+
// way a navigation does. Guarded, so a query the shopper already typed past
|
|
10086
|
+
// is not counted.
|
|
10087
|
+
this.flushSettledQuery();
|
|
10077
10088
|
clearTimeout(this.autocompleteTimer);
|
|
10078
10089
|
clearInterval(this.keepWarmTimer);
|
|
10079
10090
|
this.keepWarmTimer = undefined;
|
|
@@ -10093,6 +10104,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10093
10104
|
(_e = this.mobileMediaQuery) === null || _e === void 0 ? void 0 : _e.removeEventListener('change', this.mobileMediaListener);
|
|
10094
10105
|
window.removeEventListener('online', this.rewarmListener);
|
|
10095
10106
|
document.removeEventListener('visibilitychange', this.rewarmListener);
|
|
10107
|
+
window.removeEventListener('pagehide', this.finalizeFlushListener);
|
|
10096
10108
|
super.disconnectedCallback();
|
|
10097
10109
|
}
|
|
10098
10110
|
willUpdate(changed) {
|
|
@@ -10417,6 +10429,13 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10417
10429
|
};
|
|
10418
10430
|
(_b = (_a = this.api) === null || _a === void 0 ? void 0 : _a.sendEvent) === null || _b === void 0 ? void 0 : _b.call(_a, 'evoSearchSearchIssued', undefined, actionData);
|
|
10419
10431
|
}
|
|
10432
|
+
// Disarms the settle without counting: whatever was on screen has been
|
|
10433
|
+
// superseded (a new query, a refinement, a failed fetch), so a later teardown
|
|
10434
|
+
// flush must not report it.
|
|
10435
|
+
cancelSettle() {
|
|
10436
|
+
clearTimeout(this.settleTimer);
|
|
10437
|
+
this.settleTimer = undefined;
|
|
10438
|
+
}
|
|
10420
10439
|
// Finalizes the query whose results are currently on screen. No-op when
|
|
10421
10440
|
// nothing rendered yet, the last fetch errored (an error count of 0 would
|
|
10422
10441
|
// mislabel the query as zero-result in the metrics), or the search pane
|
|
@@ -10424,7 +10443,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10424
10443
|
// the Ask pane is on screen, and finalizing it from there would double-count
|
|
10425
10444
|
// a query that was already finalized when the shopper switched tabs.
|
|
10426
10445
|
finalizeSettledQuery() {
|
|
10427
|
-
|
|
10446
|
+
this.cancelSettle();
|
|
10428
10447
|
if (this.hasError || !this.debouncedQuery || this.mode !== 'search') {
|
|
10429
10448
|
return;
|
|
10430
10449
|
}
|
|
@@ -10435,9 +10454,20 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10435
10454
|
this.markQueryFinalized(this.debouncedQuery, this.searchProducts.length + this.searchCollections.length);
|
|
10436
10455
|
}
|
|
10437
10456
|
startSettleTimer() {
|
|
10438
|
-
|
|
10457
|
+
this.cancelSettle();
|
|
10439
10458
|
this.settleTimer = setTimeout(() => this.finalizeSettledQuery(), SEARCH_SETTLE_MS);
|
|
10440
10459
|
}
|
|
10460
|
+
// Teardown/navigation flush. Only an ARMED settle counts: a shopper who
|
|
10461
|
+
// clicked a result inside the pause performed the search, but one who had
|
|
10462
|
+
// already typed past it (the timer was disarmed and a newer fetch is in
|
|
10463
|
+
// flight) did not, and `debouncedQuery` still holds the old text. The hook
|
|
10464
|
+
// refuses the same case by dropping its pending record on supersession.
|
|
10465
|
+
flushSettledQuery() {
|
|
10466
|
+
if (this.settleTimer === undefined) {
|
|
10467
|
+
return;
|
|
10468
|
+
}
|
|
10469
|
+
this.finalizeSettledQuery();
|
|
10470
|
+
}
|
|
10441
10471
|
handleSearchInput(e) {
|
|
10442
10472
|
var _a, _b, _c;
|
|
10443
10473
|
const { query } = e.detail;
|
|
@@ -10463,7 +10493,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10463
10493
|
if (this.attachedImage) {
|
|
10464
10494
|
this.loading = true;
|
|
10465
10495
|
// The refinement supersedes whatever the previous query was settling.
|
|
10466
|
-
|
|
10496
|
+
this.cancelSettle();
|
|
10467
10497
|
this.finalizeOnRender = false;
|
|
10468
10498
|
clearTimeout(this.autocompleteTimer);
|
|
10469
10499
|
this.requestGeneration += 1;
|
|
@@ -10504,7 +10534,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10504
10534
|
return;
|
|
10505
10535
|
}
|
|
10506
10536
|
// The query changed — whatever was settling is no longer settled.
|
|
10507
|
-
|
|
10537
|
+
this.cancelSettle();
|
|
10508
10538
|
this.finalizeOnRender = false;
|
|
10509
10539
|
if (trimmed.length >= MIN_QUERY_LENGTH) {
|
|
10510
10540
|
if (!this.hasWarmedForInput) {
|
|
@@ -10598,7 +10628,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10598
10628
|
this.debouncedQuery = query;
|
|
10599
10629
|
this.loading = false;
|
|
10600
10630
|
this.finalizeOnRender = false;
|
|
10601
|
-
|
|
10631
|
+
this.cancelSettle();
|
|
10602
10632
|
}
|
|
10603
10633
|
}
|
|
10604
10634
|
// Ask-mode completions are navigation hints only: populate suggestions
|
|
@@ -11272,7 +11302,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
11272
11302
|
// The applied query supersedes whatever was settling; without this the
|
|
11273
11303
|
// old query's settle timer races the new fetch and finalizes
|
|
11274
11304
|
// nondeterministically.
|
|
11275
|
-
|
|
11305
|
+
this.cancelSettle();
|
|
11276
11306
|
this.finalizeOnRender = false;
|
|
11277
11307
|
this.isHandlingSuggestionClick = true;
|
|
11278
11308
|
this.searchQuery = value;
|
package/index.js
CHANGED
|
@@ -2097,7 +2097,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
2097
2097
|
const tag = ({ data }) => {
|
|
2098
2098
|
try {
|
|
2099
2099
|
return {
|
|
2100
|
-
sdkVersion: "1.
|
|
2100
|
+
sdkVersion: "1.69.0" || '',
|
|
2101
2101
|
searchQuery: data['searchQuery'],
|
|
2102
2102
|
searchResults: data['searchResults'],
|
|
2103
2103
|
timestamp: Date.now(),
|
|
@@ -2106,7 +2106,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
2106
2106
|
catch (err) {
|
|
2107
2107
|
console.error(`EvoSearch SDK: Tagging failed - ${err}`);
|
|
2108
2108
|
return {
|
|
2109
|
-
sdkVersion: "1.
|
|
2109
|
+
sdkVersion: "1.69.0" ,
|
|
2110
2110
|
timestamp: Date.now(),
|
|
2111
2111
|
};
|
|
2112
2112
|
}
|
|
@@ -9871,6 +9871,8 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
9871
9871
|
};
|
|
9872
9872
|
this.isHandlingSuggestionClick = false;
|
|
9873
9873
|
this.cachedRailSections = [];
|
|
9874
|
+
// Bound once so connected/disconnected add and remove the SAME reference.
|
|
9875
|
+
this.finalizeFlushListener = () => this.flushSettledQuery();
|
|
9874
9876
|
// Last finalized query (lowercased) — consecutive duplicates (settle
|
|
9875
9877
|
// followed by Enter on the same query) collapse into one search.
|
|
9876
9878
|
this.lastFinalizedQuery = '';
|
|
@@ -10036,6 +10038,12 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10036
10038
|
this.mobileMediaQuery.addEventListener('change', this.mobileMediaListener);
|
|
10037
10039
|
window.addEventListener('online', this.rewarmListener);
|
|
10038
10040
|
document.addEventListener('visibilitychange', this.rewarmListener);
|
|
10041
|
+
// A product card is a plain anchor, so a click inside the settle window is
|
|
10042
|
+
// a full navigation that would otherwise kill the pending finalize — and
|
|
10043
|
+
// a fast click-through is the most successful search there is. sendEvent
|
|
10044
|
+
// posts with keepalive, so flushing from pagehide survives the unload;
|
|
10045
|
+
// markQueryFinalized's dedupe keeps a bfcache return from doubling it.
|
|
10046
|
+
window.addEventListener('pagehide', this.finalizeFlushListener);
|
|
10039
10047
|
// Restart keep-warm when the same instance re-attaches (a host re-append
|
|
10040
10048
|
// moves the subtree and fires disconnected→connected without a fresh
|
|
10041
10049
|
// firstUpdated). On the very first connect `api` may not be bound yet —
|
|
@@ -10073,8 +10081,11 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10073
10081
|
}
|
|
10074
10082
|
disconnectedCallback() {
|
|
10075
10083
|
var _a, _b, _c, _d, _e;
|
|
10076
|
-
// Closing the widget
|
|
10077
|
-
|
|
10084
|
+
// Closing the widget — and a host-driven teardown that removes the overlay
|
|
10085
|
+
// mid-settle — settles whatever query was waiting out its pause, the same
|
|
10086
|
+
// way a navigation does. Guarded, so a query the shopper already typed past
|
|
10087
|
+
// is not counted.
|
|
10088
|
+
this.flushSettledQuery();
|
|
10078
10089
|
clearTimeout(this.autocompleteTimer);
|
|
10079
10090
|
clearInterval(this.keepWarmTimer);
|
|
10080
10091
|
this.keepWarmTimer = undefined;
|
|
@@ -10094,6 +10105,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10094
10105
|
(_e = this.mobileMediaQuery) === null || _e === void 0 ? void 0 : _e.removeEventListener('change', this.mobileMediaListener);
|
|
10095
10106
|
window.removeEventListener('online', this.rewarmListener);
|
|
10096
10107
|
document.removeEventListener('visibilitychange', this.rewarmListener);
|
|
10108
|
+
window.removeEventListener('pagehide', this.finalizeFlushListener);
|
|
10097
10109
|
super.disconnectedCallback();
|
|
10098
10110
|
}
|
|
10099
10111
|
willUpdate(changed) {
|
|
@@ -10418,6 +10430,13 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10418
10430
|
};
|
|
10419
10431
|
(_b = (_a = this.api) === null || _a === void 0 ? void 0 : _a.sendEvent) === null || _b === void 0 ? void 0 : _b.call(_a, 'evoSearchSearchIssued', undefined, actionData);
|
|
10420
10432
|
}
|
|
10433
|
+
// Disarms the settle without counting: whatever was on screen has been
|
|
10434
|
+
// superseded (a new query, a refinement, a failed fetch), so a later teardown
|
|
10435
|
+
// flush must not report it.
|
|
10436
|
+
cancelSettle() {
|
|
10437
|
+
clearTimeout(this.settleTimer);
|
|
10438
|
+
this.settleTimer = undefined;
|
|
10439
|
+
}
|
|
10421
10440
|
// Finalizes the query whose results are currently on screen. No-op when
|
|
10422
10441
|
// nothing rendered yet, the last fetch errored (an error count of 0 would
|
|
10423
10442
|
// mislabel the query as zero-result in the metrics), or the search pane
|
|
@@ -10425,7 +10444,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10425
10444
|
// the Ask pane is on screen, and finalizing it from there would double-count
|
|
10426
10445
|
// a query that was already finalized when the shopper switched tabs.
|
|
10427
10446
|
finalizeSettledQuery() {
|
|
10428
|
-
|
|
10447
|
+
this.cancelSettle();
|
|
10429
10448
|
if (this.hasError || !this.debouncedQuery || this.mode !== 'search') {
|
|
10430
10449
|
return;
|
|
10431
10450
|
}
|
|
@@ -10436,9 +10455,20 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10436
10455
|
this.markQueryFinalized(this.debouncedQuery, this.searchProducts.length + this.searchCollections.length);
|
|
10437
10456
|
}
|
|
10438
10457
|
startSettleTimer() {
|
|
10439
|
-
|
|
10458
|
+
this.cancelSettle();
|
|
10440
10459
|
this.settleTimer = setTimeout(() => this.finalizeSettledQuery(), SEARCH_SETTLE_MS);
|
|
10441
10460
|
}
|
|
10461
|
+
// Teardown/navigation flush. Only an ARMED settle counts: a shopper who
|
|
10462
|
+
// clicked a result inside the pause performed the search, but one who had
|
|
10463
|
+
// already typed past it (the timer was disarmed and a newer fetch is in
|
|
10464
|
+
// flight) did not, and `debouncedQuery` still holds the old text. The hook
|
|
10465
|
+
// refuses the same case by dropping its pending record on supersession.
|
|
10466
|
+
flushSettledQuery() {
|
|
10467
|
+
if (this.settleTimer === undefined) {
|
|
10468
|
+
return;
|
|
10469
|
+
}
|
|
10470
|
+
this.finalizeSettledQuery();
|
|
10471
|
+
}
|
|
10442
10472
|
handleSearchInput(e) {
|
|
10443
10473
|
var _a, _b, _c;
|
|
10444
10474
|
const { query } = e.detail;
|
|
@@ -10464,7 +10494,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10464
10494
|
if (this.attachedImage) {
|
|
10465
10495
|
this.loading = true;
|
|
10466
10496
|
// The refinement supersedes whatever the previous query was settling.
|
|
10467
|
-
|
|
10497
|
+
this.cancelSettle();
|
|
10468
10498
|
this.finalizeOnRender = false;
|
|
10469
10499
|
clearTimeout(this.autocompleteTimer);
|
|
10470
10500
|
this.requestGeneration += 1;
|
|
@@ -10505,7 +10535,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10505
10535
|
return;
|
|
10506
10536
|
}
|
|
10507
10537
|
// The query changed — whatever was settling is no longer settled.
|
|
10508
|
-
|
|
10538
|
+
this.cancelSettle();
|
|
10509
10539
|
this.finalizeOnRender = false;
|
|
10510
10540
|
if (trimmed.length >= MIN_QUERY_LENGTH) {
|
|
10511
10541
|
if (!this.hasWarmedForInput) {
|
|
@@ -10599,7 +10629,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10599
10629
|
this.debouncedQuery = query;
|
|
10600
10630
|
this.loading = false;
|
|
10601
10631
|
this.finalizeOnRender = false;
|
|
10602
|
-
|
|
10632
|
+
this.cancelSettle();
|
|
10603
10633
|
}
|
|
10604
10634
|
}
|
|
10605
10635
|
// Ask-mode completions are navigation hints only: populate suggestions
|
|
@@ -11273,7 +11303,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
11273
11303
|
// The applied query supersedes whatever was settling; without this the
|
|
11274
11304
|
// old query's settle timer races the new fetch and finalizes
|
|
11275
11305
|
// nondeterministically.
|
|
11276
|
-
|
|
11306
|
+
this.cancelSettle();
|
|
11277
11307
|
this.finalizeOnRender = false;
|
|
11278
11308
|
this.isHandlingSuggestionClick = true;
|
|
11279
11309
|
this.searchQuery = value;
|
package/index.mjs
CHANGED
|
@@ -2094,7 +2094,7 @@ const init = (params) => {
|
|
|
2094
2094
|
const tag = ({ data }) => {
|
|
2095
2095
|
try {
|
|
2096
2096
|
return {
|
|
2097
|
-
sdkVersion: "1.
|
|
2097
|
+
sdkVersion: "1.69.0" || '',
|
|
2098
2098
|
searchQuery: data['searchQuery'],
|
|
2099
2099
|
searchResults: data['searchResults'],
|
|
2100
2100
|
timestamp: Date.now(),
|
|
@@ -2103,7 +2103,7 @@ const tag = ({ data }) => {
|
|
|
2103
2103
|
catch (err) {
|
|
2104
2104
|
console.error(`EvoSearch SDK: Tagging failed - ${err}`);
|
|
2105
2105
|
return {
|
|
2106
|
-
sdkVersion: "1.
|
|
2106
|
+
sdkVersion: "1.69.0" ,
|
|
2107
2107
|
timestamp: Date.now(),
|
|
2108
2108
|
};
|
|
2109
2109
|
}
|
|
@@ -9868,6 +9868,8 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
9868
9868
|
};
|
|
9869
9869
|
this.isHandlingSuggestionClick = false;
|
|
9870
9870
|
this.cachedRailSections = [];
|
|
9871
|
+
// Bound once so connected/disconnected add and remove the SAME reference.
|
|
9872
|
+
this.finalizeFlushListener = () => this.flushSettledQuery();
|
|
9871
9873
|
// Last finalized query (lowercased) — consecutive duplicates (settle
|
|
9872
9874
|
// followed by Enter on the same query) collapse into one search.
|
|
9873
9875
|
this.lastFinalizedQuery = '';
|
|
@@ -10033,6 +10035,12 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10033
10035
|
this.mobileMediaQuery.addEventListener('change', this.mobileMediaListener);
|
|
10034
10036
|
window.addEventListener('online', this.rewarmListener);
|
|
10035
10037
|
document.addEventListener('visibilitychange', this.rewarmListener);
|
|
10038
|
+
// A product card is a plain anchor, so a click inside the settle window is
|
|
10039
|
+
// a full navigation that would otherwise kill the pending finalize — and
|
|
10040
|
+
// a fast click-through is the most successful search there is. sendEvent
|
|
10041
|
+
// posts with keepalive, so flushing from pagehide survives the unload;
|
|
10042
|
+
// markQueryFinalized's dedupe keeps a bfcache return from doubling it.
|
|
10043
|
+
window.addEventListener('pagehide', this.finalizeFlushListener);
|
|
10036
10044
|
// Restart keep-warm when the same instance re-attaches (a host re-append
|
|
10037
10045
|
// moves the subtree and fires disconnected→connected without a fresh
|
|
10038
10046
|
// firstUpdated). On the very first connect `api` may not be bound yet —
|
|
@@ -10070,8 +10078,11 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10070
10078
|
}
|
|
10071
10079
|
disconnectedCallback() {
|
|
10072
10080
|
var _a, _b, _c, _d, _e;
|
|
10073
|
-
// Closing the widget
|
|
10074
|
-
|
|
10081
|
+
// Closing the widget — and a host-driven teardown that removes the overlay
|
|
10082
|
+
// mid-settle — settles whatever query was waiting out its pause, the same
|
|
10083
|
+
// way a navigation does. Guarded, so a query the shopper already typed past
|
|
10084
|
+
// is not counted.
|
|
10085
|
+
this.flushSettledQuery();
|
|
10075
10086
|
clearTimeout(this.autocompleteTimer);
|
|
10076
10087
|
clearInterval(this.keepWarmTimer);
|
|
10077
10088
|
this.keepWarmTimer = undefined;
|
|
@@ -10091,6 +10102,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10091
10102
|
(_e = this.mobileMediaQuery) === null || _e === void 0 ? void 0 : _e.removeEventListener('change', this.mobileMediaListener);
|
|
10092
10103
|
window.removeEventListener('online', this.rewarmListener);
|
|
10093
10104
|
document.removeEventListener('visibilitychange', this.rewarmListener);
|
|
10105
|
+
window.removeEventListener('pagehide', this.finalizeFlushListener);
|
|
10094
10106
|
super.disconnectedCallback();
|
|
10095
10107
|
}
|
|
10096
10108
|
willUpdate(changed) {
|
|
@@ -10415,6 +10427,13 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10415
10427
|
};
|
|
10416
10428
|
(_b = (_a = this.api) === null || _a === void 0 ? void 0 : _a.sendEvent) === null || _b === void 0 ? void 0 : _b.call(_a, 'evoSearchSearchIssued', undefined, actionData);
|
|
10417
10429
|
}
|
|
10430
|
+
// Disarms the settle without counting: whatever was on screen has been
|
|
10431
|
+
// superseded (a new query, a refinement, a failed fetch), so a later teardown
|
|
10432
|
+
// flush must not report it.
|
|
10433
|
+
cancelSettle() {
|
|
10434
|
+
clearTimeout(this.settleTimer);
|
|
10435
|
+
this.settleTimer = undefined;
|
|
10436
|
+
}
|
|
10418
10437
|
// Finalizes the query whose results are currently on screen. No-op when
|
|
10419
10438
|
// nothing rendered yet, the last fetch errored (an error count of 0 would
|
|
10420
10439
|
// mislabel the query as zero-result in the metrics), or the search pane
|
|
@@ -10422,7 +10441,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10422
10441
|
// the Ask pane is on screen, and finalizing it from there would double-count
|
|
10423
10442
|
// a query that was already finalized when the shopper switched tabs.
|
|
10424
10443
|
finalizeSettledQuery() {
|
|
10425
|
-
|
|
10444
|
+
this.cancelSettle();
|
|
10426
10445
|
if (this.hasError || !this.debouncedQuery || this.mode !== 'search') {
|
|
10427
10446
|
return;
|
|
10428
10447
|
}
|
|
@@ -10433,9 +10452,20 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10433
10452
|
this.markQueryFinalized(this.debouncedQuery, this.searchProducts.length + this.searchCollections.length);
|
|
10434
10453
|
}
|
|
10435
10454
|
startSettleTimer() {
|
|
10436
|
-
|
|
10455
|
+
this.cancelSettle();
|
|
10437
10456
|
this.settleTimer = setTimeout(() => this.finalizeSettledQuery(), SEARCH_SETTLE_MS);
|
|
10438
10457
|
}
|
|
10458
|
+
// Teardown/navigation flush. Only an ARMED settle counts: a shopper who
|
|
10459
|
+
// clicked a result inside the pause performed the search, but one who had
|
|
10460
|
+
// already typed past it (the timer was disarmed and a newer fetch is in
|
|
10461
|
+
// flight) did not, and `debouncedQuery` still holds the old text. The hook
|
|
10462
|
+
// refuses the same case by dropping its pending record on supersession.
|
|
10463
|
+
flushSettledQuery() {
|
|
10464
|
+
if (this.settleTimer === undefined) {
|
|
10465
|
+
return;
|
|
10466
|
+
}
|
|
10467
|
+
this.finalizeSettledQuery();
|
|
10468
|
+
}
|
|
10439
10469
|
handleSearchInput(e) {
|
|
10440
10470
|
var _a, _b, _c;
|
|
10441
10471
|
const { query } = e.detail;
|
|
@@ -10461,7 +10491,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10461
10491
|
if (this.attachedImage) {
|
|
10462
10492
|
this.loading = true;
|
|
10463
10493
|
// The refinement supersedes whatever the previous query was settling.
|
|
10464
|
-
|
|
10494
|
+
this.cancelSettle();
|
|
10465
10495
|
this.finalizeOnRender = false;
|
|
10466
10496
|
clearTimeout(this.autocompleteTimer);
|
|
10467
10497
|
this.requestGeneration += 1;
|
|
@@ -10502,7 +10532,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10502
10532
|
return;
|
|
10503
10533
|
}
|
|
10504
10534
|
// The query changed — whatever was settling is no longer settled.
|
|
10505
|
-
|
|
10535
|
+
this.cancelSettle();
|
|
10506
10536
|
this.finalizeOnRender = false;
|
|
10507
10537
|
if (trimmed.length >= MIN_QUERY_LENGTH) {
|
|
10508
10538
|
if (!this.hasWarmedForInput) {
|
|
@@ -10596,7 +10626,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
10596
10626
|
this.debouncedQuery = query;
|
|
10597
10627
|
this.loading = false;
|
|
10598
10628
|
this.finalizeOnRender = false;
|
|
10599
|
-
|
|
10629
|
+
this.cancelSettle();
|
|
10600
10630
|
}
|
|
10601
10631
|
}
|
|
10602
10632
|
// Ask-mode completions are navigation hints only: populate suggestions
|
|
@@ -11270,7 +11300,7 @@ class SearchWidget extends ElementWithStylesheet {
|
|
|
11270
11300
|
// The applied query supersedes whatever was settling; without this the
|
|
11271
11301
|
// old query's settle timer races the new fetch and finalizes
|
|
11272
11302
|
// nondeterministically.
|
|
11273
|
-
|
|
11303
|
+
this.cancelSettle();
|
|
11274
11304
|
this.finalizeOnRender = false;
|
|
11275
11305
|
this.isHandlingSuggestionClick = true;
|
|
11276
11306
|
this.searchQuery = value;
|