@blotoutio/providers-evo-search-sdk 1.65.2 → 1.66.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 -0
- package/core.js +2 -0
- package/core.mjs +2 -0
- package/hooks.d.ts +8 -0
- package/index.cjs.js +279 -45
- package/index.js +279 -45
- package/index.mjs +279 -45
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
var ProvidersEvoSearchSdk = (function () {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
// When the Search tab offers Ask AI alongside its results. `always`: on every
|
|
5
|
+
// result set (the default — the tab pill alone doesn't read as an offer).
|
|
6
|
+
// `weak`: only when retrieval had no lexical anchor, i.e. the response's
|
|
7
|
+
// `signals.bm25` is false, so the products on screen are a semantic guess.
|
|
8
|
+
// `never`: the search results never mention Ask AI (the zero-result recovery
|
|
9
|
+
// CTA is unaffected — that one is a dead end, not a nudge).
|
|
10
|
+
// The tuple is the single source of truth: the API payload enum, the worker's
|
|
11
|
+
// write validator and the manifest coercion all derive from it, so adding a
|
|
12
|
+
// mode can't leave one layer rejecting it.
|
|
13
|
+
// Default on: the segmented tab pill alone doesn't read as an offer, which is
|
|
14
|
+
// the whole reason the nudge exists. Merchants dial it back to 'weak'/'never'.
|
|
15
|
+
const DEFAULT_ASK_NUDGE_MODE = 'always';
|
|
16
|
+
// Merchant-authored label for the nudge. `{query}` is substituted with the
|
|
17
|
+
// shopper's search terms; a label without the token renders verbatim, which is
|
|
18
|
+
// the escape hatch for merchants who don't want the query echoed back.
|
|
19
|
+
// Substitution happens through Lit text bindings, so the query is escaped —
|
|
20
|
+
// never interpolate this into markup.
|
|
21
|
+
const ASK_NUDGE_QUERY_TOKEN = '{query}';
|
|
22
|
+
const DEFAULT_ASK_NUDGE_LABEL = `Ask AI about ${ASK_NUDGE_QUERY_TOKEN}`;
|
|
23
|
+
|
|
4
24
|
/**
|
|
5
25
|
* Known ad-network click ID query parameters and the human-readable provider
|
|
6
26
|
* label each one maps to. Used by the CDN worker to resolve `inSessionTouch`
|
|
@@ -631,7 +651,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
631
651
|
const searchSessionKeyName = 'evoSearchSession';
|
|
632
652
|
const SEARCH_SESSION_VERSION = 1;
|
|
633
653
|
const getStoredSearchSession = () => {
|
|
634
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
654
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
635
655
|
try {
|
|
636
656
|
const raw = sessionStorage.getItem(searchSessionKeyName);
|
|
637
657
|
if (!raw) {
|
|
@@ -653,10 +673,12 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
653
673
|
suggestions: (_a = parsed.suggestions) !== null && _a !== void 0 ? _a : [],
|
|
654
674
|
products: (_b = parsed.products) !== null && _b !== void 0 ? _b : [],
|
|
655
675
|
collections: (_c = parsed.collections) !== null && _c !== void 0 ? _c : [],
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
676
|
+
// Absent on pre-nudge snapshots — true matches the widget's own default.
|
|
677
|
+
hadLexicalMatch: (_d = parsed.hadLexicalMatch) !== null && _d !== void 0 ? _d : true,
|
|
678
|
+
askAnswer: (_e = parsed.askAnswer) !== null && _e !== void 0 ? _e : '',
|
|
679
|
+
askEvidence: (_f = parsed.askEvidence) !== null && _f !== void 0 ? _f : null,
|
|
680
|
+
askFollowUps: (_g = parsed.askFollowUps) !== null && _g !== void 0 ? _g : [],
|
|
681
|
+
askCitedTitles: (_h = parsed.askCitedTitles) !== null && _h !== void 0 ? _h : [],
|
|
660
682
|
};
|
|
661
683
|
}
|
|
662
684
|
catch {
|
|
@@ -1430,6 +1452,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
1430
1452
|
const tag = ({ data }) => {
|
|
1431
1453
|
try {
|
|
1432
1454
|
return {
|
|
1455
|
+
sdkVersion: "1.66.0" || '',
|
|
1433
1456
|
searchQuery: data['searchQuery'],
|
|
1434
1457
|
searchResults: data['searchResults'],
|
|
1435
1458
|
timestamp: Date.now(),
|
|
@@ -1438,6 +1461,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
1438
1461
|
catch (err) {
|
|
1439
1462
|
console.error(`EvoSearch SDK: Tagging failed - ${err}`);
|
|
1440
1463
|
return {
|
|
1464
|
+
sdkVersion: "1.66.0" ,
|
|
1441
1465
|
timestamp: Date.now(),
|
|
1442
1466
|
};
|
|
1443
1467
|
}
|
|
@@ -7567,12 +7591,12 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
7567
7591
|
|
|
7568
7592
|
/* The AI rescue is the primary recovery on a dead-end search. */
|
|
7569
7593
|
.chip-ask {
|
|
7570
|
-
background: var(--evosearch-ask-accent, #
|
|
7594
|
+
background: var(--evosearch-ask-accent, #7c3aed);
|
|
7571
7595
|
color: #ffffff;
|
|
7572
7596
|
}
|
|
7573
7597
|
|
|
7574
7598
|
.chip-ask:hover {
|
|
7575
|
-
background: var(--evosearch-ask-accent, #
|
|
7599
|
+
background: var(--evosearch-ask-accent, #7c3aed);
|
|
7576
7600
|
filter: brightness(0.95);
|
|
7577
7601
|
}
|
|
7578
7602
|
|
|
@@ -7841,7 +7865,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
7841
7865
|
/* Ask mode swaps the active accent; everything inside the container reads
|
|
7842
7866
|
var(--evosearch-mode-accent) (custom props inherit to evs-box + cards). */
|
|
7843
7867
|
.search-widget-container[data-mode='ask'] {
|
|
7844
|
-
--evosearch-mode-accent: var(--evosearch-ask-accent, #
|
|
7868
|
+
--evosearch-mode-accent: var(--evosearch-ask-accent, #7c3aed);
|
|
7845
7869
|
}
|
|
7846
7870
|
|
|
7847
7871
|
/* The theme font reaches form controls (which don't inherit it by default). */
|
|
@@ -8091,6 +8115,12 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
8091
8115
|
max-height: 0;
|
|
8092
8116
|
opacity: 0;
|
|
8093
8117
|
pointer-events: none;
|
|
8118
|
+
/* The collapsed row is still a .body child, so the sibling rule above gives
|
|
8119
|
+
it a top margin AND another to whatever follows — 2.25em of dead space
|
|
8120
|
+
stacked around a zero-height element, which reads as a broken gap between
|
|
8121
|
+
the nudge and the results heading. Drop its own margin so the only gap
|
|
8122
|
+
left is the single one the next section is entitled to. */
|
|
8123
|
+
margin-top: 0;
|
|
8094
8124
|
}
|
|
8095
8125
|
|
|
8096
8126
|
/* inline-block (not flex) so the typed span and the bold completion stay on
|
|
@@ -8218,9 +8248,6 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
8218
8248
|
|
|
8219
8249
|
/* Segmented Results/AI toggle — replaces the "Search with AI" CTA once an
|
|
8220
8250
|
answer is in memory, letting the shopper flip between the two views. */
|
|
8221
|
-
/* Dual-tone segmented control: two equal halves with a thumb that slides
|
|
8222
|
-
between them (driven by the container's data-mode). The thumb is neutral
|
|
8223
|
-
under Search and gains an amethyst→indigo wash under Ask AI. */
|
|
8224
8251
|
/* Compact segmented control living inside the search bar. Two equal halves
|
|
8225
8252
|
(grid 1fr 1fr) sized to their content so the sliding thumb stays aligned
|
|
8226
8253
|
at 50%; flex:0 0 auto keeps it from stretching in the header row. */
|
|
@@ -8272,13 +8299,17 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
8272
8299
|
.search-widget-container[data-mode='ask'] .view-toggle::before {
|
|
8273
8300
|
transform: translateX(100%);
|
|
8274
8301
|
border-radius: 0 999px 999px 0;
|
|
8275
|
-
background:
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8302
|
+
background: color-mix(
|
|
8303
|
+
in srgb,
|
|
8304
|
+
var(--evosearch-ask-accent, #7c3aed) 10%,
|
|
8305
|
+
#ffffff
|
|
8306
|
+
);
|
|
8307
|
+
box-shadow: 0 1px 4px
|
|
8308
|
+
color-mix(
|
|
8309
|
+
in srgb,
|
|
8310
|
+
var(--evosearch-ask-accent, #7c3aed) 22%,
|
|
8311
|
+
transparent
|
|
8312
|
+
),
|
|
8282
8313
|
inset 0 -0.5px 0.5px rgba(10, 13, 18, 0.08);
|
|
8283
8314
|
}
|
|
8284
8315
|
|
|
@@ -8314,11 +8345,13 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
8314
8345
|
}
|
|
8315
8346
|
|
|
8316
8347
|
.view-toggle-search:focus-visible {
|
|
8317
|
-
box-shadow: 0 0 0 3px
|
|
8348
|
+
box-shadow: 0 0 0 3px
|
|
8349
|
+
color-mix(in srgb, var(--evosearch-accent, #e0590b) 30%, transparent);
|
|
8318
8350
|
}
|
|
8319
8351
|
|
|
8320
8352
|
.view-toggle-ai:focus-visible {
|
|
8321
|
-
box-shadow: 0 0 0 3px
|
|
8353
|
+
box-shadow: 0 0 0 3px
|
|
8354
|
+
color-mix(in srgb, var(--evosearch-ask-accent, #7c3aed) 30%, transparent);
|
|
8322
8355
|
}
|
|
8323
8356
|
|
|
8324
8357
|
.view-toggle-search:hover:not(.is-active) {
|
|
@@ -8326,16 +8359,19 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
8326
8359
|
}
|
|
8327
8360
|
|
|
8328
8361
|
.view-toggle-ai:hover:not(.is-active) {
|
|
8329
|
-
color: #
|
|
8362
|
+
color: var(--evosearch-ask-accent, #7c3aed);
|
|
8330
8363
|
}
|
|
8331
8364
|
|
|
8332
|
-
/*
|
|
8365
|
+
/* Each half carries its own accent when active: Search the brand accent, Ask
|
|
8366
|
+
the --evosearch-ask-accent that state-block, the ask-answer rule and the
|
|
8367
|
+
follow-up chips already use — so one colour means "AI" everywhere rather
|
|
8368
|
+
than the toggle inventing a second one. */
|
|
8333
8369
|
.view-toggle-search.is-active {
|
|
8334
|
-
color: var(--evosearch-accent, #
|
|
8370
|
+
color: var(--evosearch-accent, #e0590b);
|
|
8335
8371
|
}
|
|
8336
8372
|
|
|
8337
8373
|
.view-toggle-ai.is-active {
|
|
8338
|
-
color: #
|
|
8374
|
+
color: var(--evosearch-ask-accent, #7c3aed);
|
|
8339
8375
|
}
|
|
8340
8376
|
|
|
8341
8377
|
.view-toggle-btn bt-lucide-icon,
|
|
@@ -8344,7 +8380,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
8344
8380
|
}
|
|
8345
8381
|
|
|
8346
8382
|
.ask-answer {
|
|
8347
|
-
border-left: 3px solid var(--evosearch-mode-accent, #
|
|
8383
|
+
border-left: 3px solid var(--evosearch-mode-accent, #7c3aed);
|
|
8348
8384
|
padding: 0 0 0 0.75em;
|
|
8349
8385
|
line-height: 1.5;
|
|
8350
8386
|
min-height: 1.5em;
|
|
@@ -8400,8 +8436,8 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
8400
8436
|
|
|
8401
8437
|
.ask-starter-chip:hover {
|
|
8402
8438
|
background: #fcfdfb;
|
|
8403
|
-
border-color: var(--evosearch-ask-accent, #
|
|
8404
|
-
color: var(--evosearch-ask-accent, #
|
|
8439
|
+
border-color: var(--evosearch-ask-accent, #7c3aed);
|
|
8440
|
+
color: var(--evosearch-ask-accent, #7c3aed);
|
|
8405
8441
|
}
|
|
8406
8442
|
|
|
8407
8443
|
.ask-starter-chip:active {
|
|
@@ -8499,6 +8535,89 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
8499
8535
|
color: rgba(26, 28, 24, 0.6);
|
|
8500
8536
|
}
|
|
8501
8537
|
|
|
8538
|
+
/* The Ask AI offer: a filled band leading the results column, directly under
|
|
8539
|
+
the search bar. It is the first thing in .body, so it is read before the
|
|
8540
|
+
grid is scanned rather than after it disappoints — and it can never be
|
|
8541
|
+
stranded in whitespace under a short result row. */
|
|
8542
|
+
.ask-nudge {
|
|
8543
|
+
appearance: none;
|
|
8544
|
+
display: flex;
|
|
8545
|
+
align-items: center;
|
|
8546
|
+
gap: 0.5em;
|
|
8547
|
+
width: 100%;
|
|
8548
|
+
padding: 0.625em 0.875em;
|
|
8549
|
+
border: 1px solid
|
|
8550
|
+
color-mix(in srgb, var(--evosearch-ask-accent, #7c3aed) 28%, transparent);
|
|
8551
|
+
border-radius: 0.625em;
|
|
8552
|
+
background: color-mix(
|
|
8553
|
+
in srgb,
|
|
8554
|
+
var(--evosearch-ask-accent, #7c3aed) 6%,
|
|
8555
|
+
#ffffff
|
|
8556
|
+
);
|
|
8557
|
+
color: var(--evosearch-ask-accent, #7c3aed);
|
|
8558
|
+
font-size: 0.8125em;
|
|
8559
|
+
font-weight: 600;
|
|
8560
|
+
text-align: left;
|
|
8561
|
+
cursor: pointer;
|
|
8562
|
+
transition: background 0.15s ease, border-color 0.15s ease;
|
|
8563
|
+
}
|
|
8564
|
+
|
|
8565
|
+
.ask-nudge:hover {
|
|
8566
|
+
background: color-mix(
|
|
8567
|
+
in srgb,
|
|
8568
|
+
var(--evosearch-ask-accent, #7c3aed) 11%,
|
|
8569
|
+
#ffffff
|
|
8570
|
+
);
|
|
8571
|
+
border-color: color-mix(
|
|
8572
|
+
in srgb,
|
|
8573
|
+
var(--evosearch-ask-accent, #7c3aed) 42%,
|
|
8574
|
+
transparent
|
|
8575
|
+
);
|
|
8576
|
+
}
|
|
8577
|
+
|
|
8578
|
+
/* The label takes the slack so the trailing arrow sits hard right, and a
|
|
8579
|
+
merchant-authored label that outruns the band ellipsises instead of
|
|
8580
|
+
wrapping it onto a third line. */
|
|
8581
|
+
.ask-nudge-text {
|
|
8582
|
+
flex: 1 1 auto;
|
|
8583
|
+
min-width: 0;
|
|
8584
|
+
overflow: hidden;
|
|
8585
|
+
text-overflow: ellipsis;
|
|
8586
|
+
white-space: nowrap;
|
|
8587
|
+
}
|
|
8588
|
+
|
|
8589
|
+
@supports (background-clip: text) or (-webkit-background-clip: text) {
|
|
8590
|
+
.ask-nudge-text {
|
|
8591
|
+
background: linear-gradient(
|
|
8592
|
+
100deg,
|
|
8593
|
+
var(--evosearch-ask-accent, #7c3aed),
|
|
8594
|
+
var(--evosearch-ask-accent-alt, #6366f1)
|
|
8595
|
+
);
|
|
8596
|
+
-webkit-background-clip: text;
|
|
8597
|
+
background-clip: text;
|
|
8598
|
+
color: transparent;
|
|
8599
|
+
}
|
|
8600
|
+
}
|
|
8601
|
+
|
|
8602
|
+
/* Amethyst→indigo sweep on the label so the offer reads as generative rather
|
|
8603
|
+
than as a plain link. Guarded by @supports because the fallback for an
|
|
8604
|
+
unsupported background-clip is transparent text, i.e. an invisible CTA —
|
|
8605
|
+
outside the query the solid ask accent from .ask-nudge stands. The sparkle
|
|
8606
|
+
and arrow icons keep currentColor: gradient-filling icon strokes at 14px
|
|
8607
|
+
muddies them, and the solid accent still reads as the same family. */
|
|
8608
|
+
@supports (background-clip: text) or (-webkit-background-clip: text) {
|
|
8609
|
+
.ask-nudge > span {
|
|
8610
|
+
background: linear-gradient(
|
|
8611
|
+
100deg,
|
|
8612
|
+
var(--evosearch-ask-accent, #7c3aed),
|
|
8613
|
+
var(--evosearch-ask-accent-alt, #6366f1)
|
|
8614
|
+
);
|
|
8615
|
+
-webkit-background-clip: text;
|
|
8616
|
+
background-clip: text;
|
|
8617
|
+
color: transparent;
|
|
8618
|
+
}
|
|
8619
|
+
}
|
|
8620
|
+
|
|
8502
8621
|
.ask-followups {
|
|
8503
8622
|
display: flex;
|
|
8504
8623
|
flex-wrap: wrap;
|
|
@@ -8524,6 +8643,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
8524
8643
|
.chip:focus-visible,
|
|
8525
8644
|
.chip-remove:focus-visible,
|
|
8526
8645
|
.suggestion:focus-visible,
|
|
8646
|
+
.ask-nudge:focus-visible,
|
|
8527
8647
|
.view-toggle-btn:focus-visible,
|
|
8528
8648
|
.bar-dismiss:focus-visible,
|
|
8529
8649
|
.ask-starter-chip:focus-visible,
|
|
@@ -8540,7 +8660,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
8540
8660
|
gap: 0.45em;
|
|
8541
8661
|
border: 0;
|
|
8542
8662
|
border-radius: 999px;
|
|
8543
|
-
background: var(--evosearch-ask-accent, #
|
|
8663
|
+
background: var(--evosearch-ask-accent, #7c3aed);
|
|
8544
8664
|
color: #ffffff;
|
|
8545
8665
|
font-size: 0.8125em;
|
|
8546
8666
|
font-weight: 600;
|
|
@@ -8636,6 +8756,11 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
8636
8756
|
this.suggestions = [];
|
|
8637
8757
|
this.searchProducts = [];
|
|
8638
8758
|
this.searchCollections = [];
|
|
8759
|
+
// Whether the current results had a lexical (BM25) anchor. False means the
|
|
8760
|
+
// grid is a purely semantic guess, which is what `askNudge: 'weak'` targets.
|
|
8761
|
+
// Defaults true so a worker that omits `signals` is treated as a normal
|
|
8762
|
+
// match rather than silently nudging on every query.
|
|
8763
|
+
this.hadLexicalMatch = true;
|
|
8639
8764
|
this.recents = [];
|
|
8640
8765
|
this.loading = false;
|
|
8641
8766
|
this.hasError = false;
|
|
@@ -9218,7 +9343,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
9218
9343
|
}));
|
|
9219
9344
|
}
|
|
9220
9345
|
async performAutocomplete(query) {
|
|
9221
|
-
var _a;
|
|
9346
|
+
var _a, _b;
|
|
9222
9347
|
const generation = ++this.requestGeneration;
|
|
9223
9348
|
this.hasError = false;
|
|
9224
9349
|
try {
|
|
@@ -9238,6 +9363,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
9238
9363
|
this.suggestions = processed.suggestions;
|
|
9239
9364
|
this.searchProducts = processed.products;
|
|
9240
9365
|
this.searchCollections = (_a = response.collections) !== null && _a !== void 0 ? _a : [];
|
|
9366
|
+
this.hadLexicalMatch = ((_b = response.signals) === null || _b === void 0 ? void 0 : _b.bm25) !== false;
|
|
9241
9367
|
this.debouncedQuery = query;
|
|
9242
9368
|
this.loading = false;
|
|
9243
9369
|
if (this.finalizeOnRender) {
|
|
@@ -9377,6 +9503,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
9377
9503
|
this.showSuggestions = false;
|
|
9378
9504
|
this.searchProducts = [];
|
|
9379
9505
|
this.searchCollections = [];
|
|
9506
|
+
this.hadLexicalMatch = true;
|
|
9380
9507
|
this.activeSuggestionIndex = -1;
|
|
9381
9508
|
// Ask mode renders from a separate state set, so clearing the query alone
|
|
9382
9509
|
// leaves the previous answer/evidence on screen. Reset it too and abort any
|
|
@@ -9478,6 +9605,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
9478
9605
|
suggestions: this.suggestions,
|
|
9479
9606
|
products: this.searchProducts,
|
|
9480
9607
|
collections: this.searchCollections,
|
|
9608
|
+
hadLexicalMatch: this.hadLexicalMatch,
|
|
9481
9609
|
askAnswer: this.askAnswer,
|
|
9482
9610
|
askEvidence: this.askEvidence,
|
|
9483
9611
|
askFollowUps: this.askFollowUps,
|
|
@@ -9498,6 +9626,9 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
9498
9626
|
this.suggestions = snap.suggestions;
|
|
9499
9627
|
this.searchProducts = snap.products;
|
|
9500
9628
|
this.searchCollections = snap.collections;
|
|
9629
|
+
// Restored too, or a purely semantic result set would look like a lexical
|
|
9630
|
+
// match after a reopen and wrongly suppress the `weak` nudge.
|
|
9631
|
+
this.hadLexicalMatch = snap.hadLexicalMatch;
|
|
9501
9632
|
// The restored query was already a real search in its first life; don't let
|
|
9502
9633
|
// the settle/finalize path re-count it as a new one.
|
|
9503
9634
|
this.lastFinalizedQuery = snap.query.trim().toLowerCase();
|
|
@@ -9543,7 +9674,11 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
9543
9674
|
${this.renderMain()}
|
|
9544
9675
|
`;
|
|
9545
9676
|
}
|
|
9677
|
+
// The nudge leads the column — directly under the search bar, above the
|
|
9678
|
+
// typeahead chips and the grid — so it is seen before the results are
|
|
9679
|
+
// scanned rather than after they disappoint.
|
|
9546
9680
|
return b `
|
|
9681
|
+
${this.shouldShowAskNudge() ? this.renderAskNudge() : A}
|
|
9547
9682
|
${this.cachedRailSections.map((section) => this.renderColumnSection(section))}
|
|
9548
9683
|
${this.renderMain()}
|
|
9549
9684
|
`;
|
|
@@ -9934,25 +10069,12 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
9934
10069
|
height="14"
|
|
9935
10070
|
viewBox="0 0 24 24"
|
|
9936
10071
|
fill="none"
|
|
9937
|
-
stroke
|
|
10072
|
+
stroke="currentColor"
|
|
9938
10073
|
stroke-width="2.2"
|
|
9939
10074
|
stroke-linecap="round"
|
|
9940
10075
|
stroke-linejoin="round"
|
|
9941
10076
|
aria-hidden="true"
|
|
9942
10077
|
>
|
|
9943
|
-
<defs>
|
|
9944
|
-
<linearGradient
|
|
9945
|
-
id="aiGrad"
|
|
9946
|
-
x1="0"
|
|
9947
|
-
y1="0"
|
|
9948
|
-
x2="24"
|
|
9949
|
-
y2="24"
|
|
9950
|
-
gradientUnits="userSpaceOnUse"
|
|
9951
|
-
>
|
|
9952
|
-
<stop offset="0" stop-color="#9333EA"></stop>
|
|
9953
|
-
<stop offset="1" stop-color="#6366F1"></stop>
|
|
9954
|
-
</linearGradient>
|
|
9955
|
-
</defs>
|
|
9956
10078
|
<path
|
|
9957
10079
|
d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"
|
|
9958
10080
|
></path>
|
|
@@ -10010,6 +10132,80 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10010
10132
|
</div>
|
|
10011
10133
|
`;
|
|
10012
10134
|
}
|
|
10135
|
+
// Whether the Ask AI offer belongs on screen right now. Lifted out of the
|
|
10136
|
+
// render so `renderColumn` can place the band above everything in the body
|
|
10137
|
+
// while still keeping the "real search results exist" conditions that only
|
|
10138
|
+
// renderMain used to know about.
|
|
10139
|
+
shouldShowAskNudge() {
|
|
10140
|
+
var _a, _b;
|
|
10141
|
+
if (!this.askActive || this.mode === 'ask') {
|
|
10142
|
+
return false;
|
|
10143
|
+
}
|
|
10144
|
+
const mode = (_b = (_a = this.uiPreferences) === null || _a === void 0 ? void 0 : _a.askNudge) !== null && _b !== void 0 ? _b : DEFAULT_ASK_NUDGE_MODE;
|
|
10145
|
+
if (mode === 'never' || (mode === 'weak' && this.hadLexicalMatch)) {
|
|
10146
|
+
return false;
|
|
10147
|
+
}
|
|
10148
|
+
// Never on the handpicked/empty state (no query to hand over), mid-load
|
|
10149
|
+
// (nothing to be unhappy with yet), on an error, or on the no-match state —
|
|
10150
|
+
// that one carries its own recovery CTA already. Collections count as
|
|
10151
|
+
// results: the same pair renderMain's `noMatches` tests, so a
|
|
10152
|
+
// collections-only match keeps the nudge rather than reading as empty.
|
|
10153
|
+
return (this.debouncedQuery.trim().length > 0 &&
|
|
10154
|
+
!this.loading &&
|
|
10155
|
+
!this.hasError &&
|
|
10156
|
+
(this.searchProducts.length > 0 || this.searchCollections.length > 0));
|
|
10157
|
+
}
|
|
10158
|
+
// Substitute the merchant's `{query}` token. Returned as separate strings so
|
|
10159
|
+
// Lit renders them as text bindings and escapes the shopper's query; never
|
|
10160
|
+
// build markup from this.
|
|
10161
|
+
askNudgeLabelParts(query) {
|
|
10162
|
+
var _a, _b;
|
|
10163
|
+
const label = ((_b = (_a = this.uiPreferences) === null || _a === void 0 ? void 0 : _a.askNudgeLabel) === null || _b === void 0 ? void 0 : _b.trim()) || DEFAULT_ASK_NUDGE_LABEL;
|
|
10164
|
+
return label.split(ASK_NUDGE_QUERY_TOKEN).join(`"${query}"`);
|
|
10165
|
+
}
|
|
10166
|
+
// Offer Ask AI above the results the shopper is about to scan. The tab pill
|
|
10167
|
+
// is too quiet to read as an offer, and the zero-result CTA never fires for
|
|
10168
|
+
// these queries by definition — this is the only place the offer lands while
|
|
10169
|
+
// the shopper still has the query in their head.
|
|
10170
|
+
renderAskNudge() {
|
|
10171
|
+
const query = this.debouncedQuery.trim();
|
|
10172
|
+
const label = this.askNudgeLabelParts(query);
|
|
10173
|
+
return b `
|
|
10174
|
+
<button
|
|
10175
|
+
type="button"
|
|
10176
|
+
class="ask-nudge"
|
|
10177
|
+
@click=${() => this.handleAskNudgeClick(query)}
|
|
10178
|
+
>
|
|
10179
|
+
<bt-lucide-icon
|
|
10180
|
+
name="sparkles"
|
|
10181
|
+
width="14"
|
|
10182
|
+
height="14"
|
|
10183
|
+
stroke="currentColor"
|
|
10184
|
+
stroke-width="1.5"
|
|
10185
|
+
></bt-lucide-icon>
|
|
10186
|
+
<span class="ask-nudge-text">${label}</span>
|
|
10187
|
+
<bt-lucide-icon
|
|
10188
|
+
name="arrowRight"
|
|
10189
|
+
width="14"
|
|
10190
|
+
height="14"
|
|
10191
|
+
stroke="currentColor"
|
|
10192
|
+
stroke-width="1.5"
|
|
10193
|
+
></bt-lucide-icon>
|
|
10194
|
+
</button>
|
|
10195
|
+
`;
|
|
10196
|
+
}
|
|
10197
|
+
// Same destination as the zero-result CTA: flip to Ask and run the query the
|
|
10198
|
+
// shopper already typed, so the words carry over verbatim.
|
|
10199
|
+
handleAskNudgeClick(query) {
|
|
10200
|
+
this.mode = 'ask';
|
|
10201
|
+
setStoredSearchMode('ask');
|
|
10202
|
+
// Reuse a held answer for this exact query, like the view toggle does —
|
|
10203
|
+
// re-submitting would open a second billed generation for the same words.
|
|
10204
|
+
const hasAnswer = this.askLoading || this.askAnswer.length > 0 || this.askEvidence !== null;
|
|
10205
|
+
if (!hasAnswer || this.lastAskedQuery !== query) {
|
|
10206
|
+
this.handleAskSubmit(query);
|
|
10207
|
+
}
|
|
10208
|
+
}
|
|
10013
10209
|
renderMain() {
|
|
10014
10210
|
var _a, _b, _c;
|
|
10015
10211
|
const askInitial = this.mode === 'ask' &&
|
|
@@ -10225,6 +10421,10 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10225
10421
|
r$1(),
|
|
10226
10422
|
__metadata("design:type", Array)
|
|
10227
10423
|
], SearchWidget.prototype, "searchCollections", void 0);
|
|
10424
|
+
__decorate([
|
|
10425
|
+
r$1(),
|
|
10426
|
+
__metadata("design:type", Object)
|
|
10427
|
+
], SearchWidget.prototype, "hadLexicalMatch", void 0);
|
|
10228
10428
|
__decorate([
|
|
10229
10429
|
r$1(),
|
|
10230
10430
|
__metadata("design:type", Array)
|
|
@@ -10284,7 +10484,15 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10284
10484
|
--evosearch-primary: #3a3d36;
|
|
10285
10485
|
--evosearch-secondary: #6b6f64;
|
|
10286
10486
|
--evosearch-accent: #e0590b;
|
|
10287
|
-
|
|
10487
|
+
/* Violet, not blue. The previous #2563eb read as a generic link colour, so
|
|
10488
|
+
every AI affordance looked like plain navigation. Violet is distinct from
|
|
10489
|
+
the brand accent AND from ordinary links, so "this is the AI" is legible
|
|
10490
|
+
without a second signal. Drives the mode toggle, the results nudge, the
|
|
10491
|
+
ask-answer rule, the follow-up chips and the state-block CTA. */
|
|
10492
|
+
--evosearch-ask-accent: #7c3aed;
|
|
10493
|
+
/* Paired end-stop for the gradient treatments (nudge label, toggle thumb).
|
|
10494
|
+
Same amethyst→indigo sweep the toggle icon used before it was unified. */
|
|
10495
|
+
--evosearch-ask-accent-alt: #6366f1;
|
|
10288
10496
|
--evosearch-mode-accent: var(--evosearch-accent);
|
|
10289
10497
|
--evosearch-white: #ffffff;
|
|
10290
10498
|
--evosearch-border: #d6d8cf;
|
|
@@ -10360,6 +10568,25 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10360
10568
|
this.handpickedProducts = [];
|
|
10361
10569
|
this.featuredCollections = [];
|
|
10362
10570
|
this.askAiEnabled = false;
|
|
10571
|
+
// Host-page scroll locks (body-scroll-lock and friends) cancel every
|
|
10572
|
+
// document-level touchmove they didn't whitelist, which kills touch
|
|
10573
|
+
// scrolling inside the widget on iOS. Keep widget touches out of their
|
|
10574
|
+
// reach; never preventDefault, or we'd kill the scroll ourselves.
|
|
10575
|
+
// Known gap: a lock registered with { capture: true } runs before the
|
|
10576
|
+
// event reaches this host, so stopPropagation can't shield against it.
|
|
10577
|
+
this.touchmoveListener = (event) => event.stopPropagation();
|
|
10578
|
+
}
|
|
10579
|
+
connectedCallback() {
|
|
10580
|
+
super.connectedCallback();
|
|
10581
|
+
// Passive: this handler only stops propagation, and a blocking touchmove
|
|
10582
|
+
// listener would keep the widget's own scrolling off the compositor.
|
|
10583
|
+
this.addEventListener('touchmove', this.touchmoveListener, {
|
|
10584
|
+
passive: true,
|
|
10585
|
+
});
|
|
10586
|
+
}
|
|
10587
|
+
disconnectedCallback() {
|
|
10588
|
+
this.removeEventListener('touchmove', this.touchmoveListener);
|
|
10589
|
+
super.disconnectedCallback();
|
|
10363
10590
|
}
|
|
10364
10591
|
updated(changedProperties) {
|
|
10365
10592
|
var _a;
|
|
@@ -10559,7 +10786,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10559
10786
|
// Runtime Ask AI config. Unlike trending (fetched once per session), this is
|
|
10560
10787
|
// fetched on every open so a merchant's dashboard change takes effect without
|
|
10561
10788
|
// a reload — there is no session-fetched latch, only an in-flight guard to
|
|
10562
|
-
// dedupe concurrent opens. `askStarters`/`askAiEnabled` apply live.
|
|
10789
|
+
// dedupe concurrent opens. `askStarters`/`askAiEnabled`/`askNudge`(+label) apply live.
|
|
10563
10790
|
const tryFetchConfig = (target) => {
|
|
10564
10791
|
if (!target.api || configController) {
|
|
10565
10792
|
return;
|
|
@@ -10577,6 +10804,13 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
10577
10804
|
...(data.askStarters !== undefined
|
|
10578
10805
|
? { askStarters: data.askStarters }
|
|
10579
10806
|
: {}),
|
|
10807
|
+
// Only overlay what the worker actually sent: during a deploy window
|
|
10808
|
+
// a new SDK can hit an old `/config` that omits these, and writing
|
|
10809
|
+
// undefined would drop the manifest-seeded value.
|
|
10810
|
+
...(data.askNudge !== undefined ? { askNudge: data.askNudge } : {}),
|
|
10811
|
+
...(data.askNudgeLabel !== undefined
|
|
10812
|
+
? { askNudgeLabel: data.askNudgeLabel }
|
|
10813
|
+
: {}),
|
|
10580
10814
|
};
|
|
10581
10815
|
}
|
|
10582
10816
|
})
|