@nosto/autocomplete
Version:
Library designed to simplify the implementation of search autocomplete functionality
937 lines (936 loc) • 21.9 kB
JavaScript
function F() {
window.nostojs = window.nostojs ?? function(e) {
(window.nostojs.q = window.nostojs.q ?? []).push(e);
};
}
async function N(e) {
return window.nostojs(e);
}
typeof window < "u" && (F(), N((e) => {
e.internal.getSettings();
})), typeof window < "u" && F();
function le(e) {
N((t) => t.recordSearchClick("autocomplete", e));
}
function fe(e) {
N((t) => t.recordSearchSubmit(e));
}
function J() {
window.nostojs = window.nostojs ?? function(e) {
(window.nostojs.q = window.nostojs.q ?? []).push(e);
};
}
async function S(e) {
return window.nostojs(e);
}
typeof window < "u" && (J(), S((e) => {
e.internal.getSettings();
})), typeof window < "u" && J();
function R(e) {
return String(e) === "[object Object]";
}
function G(e) {
if (!R(e))
return !1;
const t = e.constructor;
if (t === void 0)
return !0;
const n = t.prototype;
return !(!R(n) || !n.hasOwnProperty("isPrototypeOf"));
}
function k(e, t) {
if (e === t)
return !0;
if (e instanceof Date && t instanceof Date)
return e.getTime() === t.getTime();
if (Array.isArray(e) && Array.isArray(t))
return e.length !== t.length ? !1 : e.every((n, r) => k(n, t[r]));
if (G(e) && G(t)) {
const n = Object.entries(e);
return n.length !== Object.keys(t).length ? !1 : n.every(([r, i]) => k(i, t[r]));
}
return !1;
}
function B() {
const e = /* @__PURE__ */ new Map();
return {
getItem(t) {
return e.get(t) ?? null;
},
setItem(t, n) {
e.set(t, n);
},
removeItem(t) {
e.delete(t);
},
clear() {
e.clear();
},
key(t) {
return Array.from(e.keys())[t] ?? null;
},
get length() {
return e.size;
}
};
}
function b(e) {
return (...t) => {
S((n) => {
(n.internal.context.mode.isPreview() ? console[e] : n.internal.logger[e])(...t);
});
};
}
const g = {
debug: b("debug"),
info: b("info"),
warn: b("warn"),
error: b("error")
};
function de(e, t, n) {
try {
const r = JSON.stringify(t);
n.setItem(e, r);
} catch (r) {
g.warn(r);
}
}
function ye(e, t) {
try {
const n = t.getItem(e);
if (n)
return JSON.parse(n);
} catch (n) {
g.warn(n);
}
}
let K, A;
try {
K = window.localStorage, A = window.sessionStorage;
} catch (e) {
g.warn(e), K = B(), A = B();
}
function ge(e, t) {
de(e, t, A);
}
function pe(e) {
return ye(e, A);
}
async function me(e, { hitDecorators: t, ...n }, r) {
const i = await r(e, n);
if (!i.products?.hits?.length || !t?.length)
return i;
const o = (a) => t.reduce((s, d) => d(s), a);
return {
...i,
products: {
...i.products,
hits: i.products.hits.map(o)
}
};
}
const Q = "nosto:search-js:cache", we = 60 * 1e3;
function he(e, t) {
ge(Q, { query: e, result: t, created: Date.now() });
}
function ve(e) {
const t = pe(Q);
if (!t || !Se(t))
return null;
const n = V(t.query);
return !k(V(e), n) || Date.now() - t.created > we ? null : t.result;
}
function V(e) {
const t = {
...e,
time: void 0,
products: {
...e.products,
size: void 0
}
};
return JSON.parse(JSON.stringify(t));
}
function Se(e) {
return typeof e == "object" && e !== null && "query" in e && "result" in e && "created" in e;
}
async function ke(e, { usePersistentCache: t, ...n }, r) {
if (!t)
return r(e, n);
const i = await be(e, n, r);
return he(e, i), i;
}
async function be(e, t, n) {
const { from: r = 0, size: i = 0 } = e.products || {}, o = ve(e);
if (!o)
return await n(e, t);
const a = o?.products?.size ?? 0, s = o?.products?.hits ?? [];
if (i === a)
return o;
if (i < a)
return {
...o,
products: {
...o.products,
size: i,
hits: s.slice(0, i),
total: o.products?.total || 0
}
};
const d = i - s.length, u = r > 0 ? r + 1 : i - d, c = {
...e,
products: {
...e.products,
from: u,
size: d
}
}, l = await n(c, t);
return {
...o,
products: {
...o.products,
size: i,
hits: [...o.products?.hits || [], ...l.products?.hits || []],
total: l.products?.total || 0
}
};
}
const Ae = 3e4, $ = /* @__PURE__ */ new Map();
function Ce(e, t) {
const n = $.get(e);
if (!n) return;
const r = Date.now() - n.created > Ae, i = k(t, n.query);
if (r || !i) {
$.delete(e);
return;
}
return n.result;
}
function Ie(e, t, n) {
$.set(e, {
query: t,
result: n,
created: Date.now()
});
}
async function De(e, t, n) {
if (!t.useMemoryCache)
return n(e, t);
const r = JSON.stringify(e), i = Ce(r, e);
if (i) return i;
const o = await n(e, t);
return Ie(r, e, o), o;
}
async function Ee(e, t, n) {
if (!t.redirect)
return n(e, t);
const r = await n(e, t);
if (r.keywords?.hits?.length) {
const i = r.keywords.hits.find((o) => o.keyword === e.query && o._redirect);
i && i._redirect && (window.location.href = i._redirect);
}
return r;
}
function Pe(e) {
return new Promise((t) => setTimeout(t, e));
}
async function Le(e, { maxRetries: t = 0, retryInterval: n = 0, ...r }, i) {
let o = 0;
for (; ; )
try {
return await i(e, r);
} catch (a) {
if (o >= t)
throw a;
if (!Ne(a))
throw g.info("Skipping retry logic for", a), a;
o++, await Pe(n);
}
}
function Ne(e) {
return !e || typeof e != "object" ? !1 : !("status" in e) || $e(e.status);
}
function $e(e) {
return typeof e == "number" && (e < 400 || e >= 500);
}
async function Oe(e, t = {}) {
const n = await new Promise(S);
return He(
n.search,
Le,
Ee,
De,
ke,
me
)(e, t);
}
function He(e, ...t) {
return t.reduce((n, r) => (i, o) => r(i, o, n), e);
}
const je = [
"productId",
"url",
"name",
"imageUrl",
"imageHash",
"thumbUrl",
"description",
"brand",
"variantId",
"availability",
"price",
"priceText",
"categoryIds",
"categories",
"customFields.key",
"customFields.value",
"priceCurrencyCode",
"datePublished",
"listPrice",
"unitPricingBaseMeasure",
"unitPricingUnit",
"unitPricingMeasure",
"googleCategory",
"gtin",
"ageGroup",
"gender",
"condition",
"alternateImageUrls",
"ratingValue",
"reviewCount",
"inventoryLevel",
"skus.id",
"skus.name",
"skus.price",
"skus.listPrice",
"skus.priceText",
"skus.url",
"skus.imageUrl",
"skus.inventoryLevel",
"skus.customFields.key",
"skus.customFields.value",
"skus.availability",
"pid",
"onDiscount",
"extra.key",
"extra.value",
"saleable",
"available",
"tags1",
"tags2",
"tags3"
];
async function O(e, t = {}) {
const n = e.products?.fields ?? je, r = e.products?.facets ?? ["*"], i = e.products?.size ?? 20, o = e.products?.from ?? 0, a = await Oe(
{
...e,
products: {
...e.products,
fields: n,
facets: r,
size: i,
from: o
}
},
t
);
return { query: e, response: a };
}
const H = {
serpPath: "/search",
queryParamName: "query",
enabled: !0
};
function W() {
return {
minQueryLength: 2,
historyEnabled: !0,
historySize: 5,
hitDecorators: [],
nostoAnalytics: !0,
googleAnalytics: H,
routingHandler: (e) => {
location.href = e;
},
nativeSubmit: !1,
submit: (e, t, n) => {
e.length >= (t.minQueryLength ?? W().minQueryLength) && O(
{
query: e
},
{
redirect: !0,
track: t.nostoAnalytics ? "serp" : void 0,
hitDecorators: t.hitDecorators,
...n
}
);
}
};
}
function qe(e, { onClick: t, onFocus: n, onInput: r, onKeyDown: i, onSubmit: o }, { form: a = e.form ?? void 0, nativeSubmit: s } = {}) {
const d = [];
function u(c, l, p) {
c.addEventListener(l, p), d.push(() => c.removeEventListener(l, p));
}
return (i || o) && u(e, "keydown", (c) => {
const l = i?.(e.value, c.key);
i && (c.key === "ArrowDown" || c.key === "ArrowUp") ? c.preventDefault() : o && c.key === "Enter" && (e.value !== "" && !c.repeat && !l && o(e.value), s || c.preventDefault());
}), o && a && (u(a, "submit", (c) => {
s || c.preventDefault(), o(e.value);
}), a.querySelectorAll("[type=submit]").forEach((c) => {
u(c, "click", (l) => {
s || l.preventDefault(), o(e.value);
});
})), t && u(e, "click", () => t(e.value)), n && u(e, "focus", () => n(e.value)), r && u(e, "input", () => r(e.value)), {
destroy() {
d.forEach((c) => c());
}
};
}
function Te(e) {
e.setAttribute("autocomplete", "off");
}
function Ue(e, t, n, r, i, o, a) {
let s = [], d = [], u = !0, c = -1;
function l(f) {
const m = f?.dataset?.nsHit;
if (m) {
const y = j(m);
if (h(), y?.item) {
r(y.item);
return;
}
if (y?.keyword) {
r(y.keyword, {
redirect: !!y?._redirect,
isKeyword: !0
}), y?._redirect && o(y._redirect);
return;
}
y?.url && o(y.url);
}
}
function p() {
u = !e.innerHTML.trim(), u || (s = Array.from(e.querySelectorAll("[data-ns-hit]")).map((f) => (te(f), f)));
}
function U() {
Object.entries(a ?? {}).map(([f, m]) => {
const y = `[data-ns-${f.replace(/([A-Z])/g, "-$1").toLowerCase()}]`;
Array.from(e.querySelectorAll(y)).map((w) => {
const ue = w?.dataset?.[`ns${f.charAt(0).toUpperCase() + f.slice(1)}`], z = () => {
m({
data: ue,
el: w,
update: _
});
};
w.addEventListener("click", z), d.push(() => {
w.removeEventListener("click", z);
});
});
});
}
function te(f) {
const m = () => {
l(f);
};
f.addEventListener("click", m), d.push(() => {
f.removeEventListener("click", m);
});
}
function E(f, m) {
if (typeof m == "number" && s[m] && s[m].classList.remove("selected"), typeof f == "number" && s[f]) {
s[f]?.classList.add("selected");
const y = s[f]?.dataset?.nsHit;
if (y) {
const w = j(y);
if (w.item) {
i(w.item);
return;
}
if (w.keyword) {
i(w.keyword);
return;
}
}
}
}
function P() {
L(), s = [], d.forEach((f) => f()), d = [];
}
async function _(f) {
P(), await n(e, f), setTimeout(() => {
p(), U(), M();
}, 0);
}
function h() {
L(), e.style.display = "none";
}
function M() {
u ? h() : e.style.display = "";
}
function ne() {
P(), u = !0, h();
}
function x() {
return e.style.display !== "none";
}
function oe() {
let f = c;
c === s.length - 1 ? c = 0 : f = c++, E(c, f);
}
function re() {
if (v()) {
let f = c;
c === 0 ? c = s.length - 1 : f = c--, E(c, f);
} else
c = s.length - 1, E(c);
}
function ie() {
x() && v() && s[c] && l(s[c]);
}
function v() {
return c > -1;
}
function ce() {
return s[c];
}
function L() {
v() && (s[c]?.classList.remove("selected"), c = -1);
}
function se() {
P(), u = !0, e.innerHTML = "";
}
async function ae() {
const f = await Promise.resolve(t);
await Promise.resolve(n(e, f)), setTimeout(() => {
p(), U(), h();
}, 0);
}
return ae(), {
update: _,
clear: ne,
isOpen: x,
goDown: oe,
goUp: re,
handleSubmit: ie,
destroy: se,
show: M,
hide: h,
resetHighlight: L,
hasHighlight: v,
getHighlight: ce,
container: e
};
}
function j(e) {
try {
return JSON.parse(e) ?? {};
} catch (t) {
return g.warn("Could not parse hit", t), {};
}
}
class Z extends Error {
}
function _e(e) {
let t = !1;
return {
promise: new Promise((n, r) => e.then(
(i) => t ? r(new Z("cancelled promise")) : n(i),
(i) => r(t ? new Z("cancelled promise") : i)
)),
cancel() {
t = !0;
}
};
}
function Me({
config: e,
history: t,
input: n
}) {
let r;
const i = (c, l, p) => typeof l.fetch == "function" ? l.fetch(c) : O(
{
query: c,
...l.fetch
},
{
track: l.nostoAnalytics ? "autocomplete" : void 0,
redirect: !1,
hitDecorators: l.hitDecorators,
...p
}
);
function o(c) {
return Promise.resolve({
query: {
query: c
},
history: t?.getItems()
});
}
function a(c, l) {
return r?.cancel(), c && c.length >= e.minQueryLength ? (r = _e(i(c, e, l)), r.promise) : t ? o(c ?? "") : (
// @ts-expect-error type mismatch
r?.promise ?? Promise.resolve({})
);
}
function s(c) {
return t && t.add(c), o(n.value);
}
function d(c) {
return t && t.remove(c), o(n.value);
}
function u() {
return t && t.clear(), o(n.value);
}
return {
updateState: a,
addHistoryItem: s,
removeHistoryItem: d,
clearHistory: u
};
}
function C(e, t) {
return (typeof e == "string" ? Array.from(document.querySelectorAll(e)) : [e]).filter((n) => t ? n instanceof t : !0);
}
function X(e, t) {
let n = [];
return C(e).forEach((r) => {
const i = r.parentNode;
i !== document && i instanceof Element && (n.push(i), n = n.concat(X(i)));
}), n.filter((r) => t === void 0);
}
function xe([e, t], n) {
const r = (i) => {
const o = i.target;
o instanceof HTMLElement && e && o !== e && o !== t && !X(o).includes(e) && n();
};
return document.addEventListener("click", r), {
destroy: () => {
document.removeEventListener("click", r);
}
};
}
const q = "nosto:autocomplete:gaEvent";
function I(e) {
const { delay: t = !1, title: n = document.title, location: r = window.location.href } = e || {};
if (t)
ze(n, r);
else {
if ("gtag" in window && typeof window.gtag == "function") {
const i = "google_tag_manager" in window && typeof window.google_tag_manager == "object" ? Object.keys(window.google_tag_manager || []).filter((o) => o.substring(0, 2) == "G-") : [];
if (i.length > 1)
for (let o = 0; o < i.length; o++)
window.gtag("event", "page_view", {
page_title: n,
page_location: r,
send_to: i[o]
});
else
window.gtag("event", "page_view", {
page_title: n,
page_location: r
});
}
if ("ga" in window && typeof window.ga == "function" && "getAll" in window.ga && typeof window.ga.getAll == "function")
try {
const i = new URL(r), o = window.ga.getAll();
o?.length > 0 && o[0]?.send("pageview", i.pathname + i.search);
} catch (i) {
g.warn("Could not send pageview to GA", i);
}
}
}
const D = (e) => typeof e.googleAnalytics == "boolean" ? e.googleAnalytics : typeof e.googleAnalytics == "object" && e.googleAnalytics.enabled, T = (e, t) => {
const n = D(t) ? typeof t.googleAnalytics == "boolean" ? H : {
...H,
...t.googleAnalytics
} : void 0;
if (e && n)
try {
return new URL(
`${n?.serpPath || location.pathname}?${`${encodeURIComponent(
n.queryParamName
)}=${encodeURIComponent(e).replace(/%20/g, "+")}`}`,
window.location.origin
).toString();
} catch (r) {
g.warn("Could not create track url", r);
return;
}
};
function ze(e, t) {
localStorage.setItem(q, JSON.stringify({ title: e, location: t }));
}
function Fe() {
const e = localStorage.getItem(q);
if (typeof e == "string") {
localStorage.removeItem(q);
try {
const t = JSON.parse(e);
I(t);
} catch (t) {
g.warn("Could not consume pageView", t);
}
}
}
function Je(e) {
let t, n = !1;
return (r) => {
n ? (t && clearTimeout(t), t = setTimeout(async () => {
await r(), n = !1;
}, e)) : (r(), n = !0);
};
}
function Re(e) {
const t = "nosto:autocomplete:history";
let n = r();
function r() {
try {
return JSON.parse(localStorage.getItem(t) ?? "[]") ?? [];
} catch (u) {
return g.error("Could not get history items.", u), [];
}
}
function i(u) {
try {
localStorage.setItem(t, JSON.stringify(u));
} catch (c) {
g.error("Could not set history items.", c);
}
}
function o(u) {
i(n = [{ item: u }, ...n?.filter((c) => c.item !== u) || []].slice(0, e));
}
function a() {
i(n = []);
}
function s(u) {
i(n = n.filter((c) => c.item !== u));
}
function d() {
return n;
}
return {
add: o,
clear: a,
remove: s,
getItems: d
};
}
function Ge(e) {
const t = {
...W(),
...e
}, n = t.historyEnabled ? Re(t.historySize) : void 0, r = Je(300);
D(e) && setTimeout(Fe, 1e3);
const i = C(e.inputSelector, HTMLInputElement).map((o) => {
const a = Me({
config: t,
history: n,
input: o
}), s = Be({
input: o,
config: t,
actions: a
});
if (!s)
return;
Te(o);
const d = qe(
o,
{
onInput: async (c) => {
r(async () => {
const l = await a.updateState(c);
s.update(l);
});
},
onClick() {
s.resetHighlight();
},
onFocus() {
s.show();
},
onSubmit() {
if (s.isOpen() && s.hasHighlight()) {
const c = s.getHighlight()?.dataset?.nsHit;
c && Y({
config: t,
data: c,
query: o.value
}), s.handleSubmit();
} else
ee({
actions: a,
config: t
})(o.value);
},
onKeyDown(c, l) {
l === "Escape" ? s.hide() : l === "ArrowDown" ? s.isOpen() ? s.goDown() : s.show() : l === "ArrowUp" && s.isOpen() && s.goUp();
}
},
{
nativeSubmit: t.nativeSubmit
}
), u = xe([s.container, o], () => {
s.hide();
});
return {
open() {
s.show();
},
close() {
s.hide();
},
destroy() {
d.destroy(), u.destroy(), s.destroy();
}
};
});
return {
destroy() {
i.forEach((o) => o?.destroy());
},
open() {
i.forEach((o) => o?.open());
},
close() {
i.forEach((o) => o?.close());
}
};
}
function Be({
input: e,
config: t,
actions: n
}) {
const r = typeof t.dropdownSelector == "function" ? C(t.dropdownSelector(e), HTMLElement) : C(t.dropdownSelector, HTMLElement);
if (r.length === 0) {
g.error(`No dropdown element found for input ${e}`);
return;
} else r.length > 1 && g.error(`Multiple dropdown elements found for input ${e}, using the first element`);
const i = r[0];
return Ue(
i,
n.updateState(e.value),
t.render,
ee({
actions: n,
config: t
}),
(o) => e.value = o,
t.routingHandler,
{
removeHistory: async function({ data: o, update: a }) {
if (o === "all") {
const s = await n.clearHistory();
return a(s);
} else if (o) {
const s = await n.removeHistoryItem(o);
return a(s);
}
},
hit: function({ data: o }) {
o && Y({ config: t, data: o, query: e.value });
}
}
);
}
async function Y({
config: e,
data: t,
query: n
}) {
if (!e.googleAnalytics && !e.nostoAnalytics)
return;
const r = j(t);
e.nostoAnalytics && r && le(r), D(e) && (r._redirect && I({
delay: !0,
location: T(r.keyword, e)
}), r.url && I({
delay: !0,
location: T(n, e)
}));
}
function ee(e) {
return async (t, n) => {
const { config: r, actions: i } = e, { redirect: o = !1 } = n ?? {};
t.length > 0 && (r.historyEnabled && i.addHistoryItem(t), r.nostoAnalytics && fe(t), D(r) && I({
delay: !0,
location: T(t, r)
}), !o && typeof r?.submit == "function" && r.submit(t, r, n));
};
}
const Ke = {
defaultCurrency: "EUR",
defaultLocale: "en-US",
/** @hidden */
currencySettings: {}
}, Qe = {
EUR: "de-DE",
GBP: "en-GB",
USD: "en-US",
AUD: "en-AU",
CAD: "en-CA",
//India, Afghanistan, Bangladesh, Bhutan, Myanmar, Nepal, and Pakistan uses lakhs and crores notation
INR: "en-IN",
AFN: "en-IN",
BDT: "en-IN",
BTN: "en-IN",
MMK: "en-IN",
NPR: "en-IN",
PKR: "en-IN"
};
function Ve(e = {}) {
const t = {
...Ke,
...e
};
e.currencySettings || S((r) => {
t.currencySettings = r.internal.getSettings().currencySettings ?? {};
});
function n(r, i) {
const { defaultCurrency: o, currencySettings: a, defaultLocale: s } = t, d = i ?? o, u = Qe[d] ?? s;
if (d in a) {
const c = a[d], l = new Intl.NumberFormat(u, {
useGrouping: !!c.groupingSeparator,
minimumFractionDigits: c.decimalPlaces,
maximumFractionDigits: c.decimalPlaces
}).formatToParts(r).map((p) => p.type === "group" ? c.groupingSeparator : p.type === "decimal" ? c.decimalCharacter : p.value).join("");
return c?.currencyBeforeAmount ? `${c.currencyToken}${l}` : `${l}${c?.currencyToken}`;
}
return new Intl.NumberFormat(u, {
style: "currency",
currency: d
}).format(r);
}
return {
formatCurrency: n
};
}
function We(e) {
const { formatCurrency: t } = Ve(e);
function n(i, o) {
const a = {};
return i.price !== void 0 && (a.priceText = t(i.price, o)), i.listPrice !== void 0 && (a.listPriceText = t(i.listPrice, o)), Object.assign({}, i, a);
}
function r(i) {
return i.price !== void 0 || i.listPrice !== void 0;
}
return function(i) {
if (!r(i))
return i;
const o = n(i, i.priceCurrencyCode);
return o.skus && o.skus.some(r) && (o.skus = o.skus.map((a) => r(a) ? n(a, i.priceCurrencyCode) : a)), o;
};
}
export {
Ge as autocomplete,
We as priceDecorator,
O as search
};
//# sourceMappingURL=autocomplete.mjs.map