@nosto/search-js 3.21.1 → 3.22.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.
@@ -8,5 +8,22 @@ export type SearchHitWithSku = SearchHit & {
8
8
  * @param type - The type of search that the hit belongs to.
9
9
  * @param hit - The search hit to add to the cart.
10
10
  * @param quantity - The quantity of the hit to add to the cart.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { addToCart } from '@nosto/search-js'
15
+ *
16
+ * // Add a single product to cart
17
+ * await addToCart('serp', {
18
+ * productId: '123',
19
+ * skuId: 'sku-123'
20
+ * })
21
+ *
22
+ * // Add multiple quantities
23
+ * await addToCart('serp', {
24
+ * productId: '456',
25
+ * skuId: 'sku-456'
26
+ * }, 3)
27
+ * ```
11
28
  */
12
29
  export declare function addToCart(type: SearchTrackOptions, hit: SearchHitWithSku, quantity?: number): Promise<void>;
@@ -6,5 +6,33 @@ import { DecoratedResult, HitDecorator, SearchOptions } from './types';
6
6
  * @param query - The search query to be executed.
7
7
  * @param options - An object containing optional parameters for the search.
8
8
  * @returns A promise that resolves to the search result.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { search } from '@nosto/search-js'
13
+ * import { priceDecorator } from '@nosto/search-js/currencies'
14
+ *
15
+ * // Basic search
16
+ * const results = await search({ query: 'shoes' })
17
+ *
18
+ * // Search with decorators
19
+ * const decoratedResults = await search(
20
+ * { query: 'shoes' },
21
+ * { hitDecorators: [priceDecorator()] }
22
+ * )
23
+ * ```
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { search } from '@nosto/search-js'
28
+ *
29
+ * // Search with filters
30
+ * const results = await search({
31
+ * query: 'shoes',
32
+ * filters: {
33
+ * color: ['red', 'blue']
34
+ * }
35
+ * })
36
+ * ```
9
37
  */
10
38
  export declare function search<HD extends readonly HitDecorator[]>(query: SearchQuery, options?: SearchOptions<HD>): Promise<DecoratedResult<HD>>;
@@ -5,6 +5,43 @@ export interface CurrencyConfig {
5
5
  defaultLocale: string;
6
6
  currencySettings: CurrencyFormats;
7
7
  }
8
+ /**
9
+ * Creates a currency formatting function with customizable settings.
10
+ *
11
+ * @param overrides - Optional configuration to override default currency settings.
12
+ * @returns An object containing the formatCurrency function.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { getCurrencyFormatting } from '@nosto/search-js/currencies'
17
+ *
18
+ * // Use default settings from Nosto
19
+ * const { formatCurrency } = getCurrencyFormatting()
20
+ * console.log(formatCurrency(1234.56)) // Uses default currency
21
+ * console.log(formatCurrency(1234.56, 'USD')) // "$1,234.56"
22
+ * ```
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * import { getCurrencyFormatting } from '@nosto/search-js/currencies'
27
+ *
28
+ * // Override with custom settings
29
+ * const { formatCurrency } = getCurrencyFormatting({
30
+ * defaultCurrency: 'EUR',
31
+ * defaultLocale: 'de-DE',
32
+ * currencySettings: {
33
+ * EUR: {
34
+ * currencyBeforeAmount: false,
35
+ * currencyToken: '€',
36
+ * decimalCharacter: ',',
37
+ * groupingSeparator: '.',
38
+ * decimalPlaces: 2
39
+ * }
40
+ * }
41
+ * })
42
+ * console.log(formatCurrency(1234.56)) // "1.234,56€"
43
+ * ```
44
+ */
8
45
  export declare function getCurrencyFormatting(overrides?: Partial<CurrencyConfig>): {
9
46
  formatCurrency: (value: number, currency?: string) => string;
10
47
  };
@@ -8,8 +8,49 @@ export type Result = SearchProduct & FormattedPrices & {
8
8
  skus?: (SearchProductSku & FormattedPrices)[];
9
9
  };
10
10
  /**
11
- * Exposes currency formatting logic as a SearchProduct decorator
12
- * Sets priceText and listPriceText fields on product and SKU level
13
- * Requires price, listPrice and priceCurrencyCode fields to be present
11
+ * Exposes currency formatting logic as a SearchProduct decorator.
12
+ * Sets priceText and listPriceText fields on product and SKU level.
13
+ * Requires price, listPrice and priceCurrencyCode fields to be present.
14
+ *
15
+ * @param config - Optional configuration to customize currency formatting.
16
+ * @returns A decorator function that adds formatted price text to search products.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { search } from '@nosto/search-js'
21
+ * import { priceDecorator } from '@nosto/search-js/currencies'
22
+ *
23
+ * // Use with default settings
24
+ * const results = await search(
25
+ * { query: 'shoes' },
26
+ * { hitDecorators: [priceDecorator()] }
27
+ * )
28
+ * console.log(results.products.hits[0].priceText) // "$99.99"
29
+ * ```
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { search } from '@nosto/search-js'
34
+ * import { priceDecorator } from '@nosto/search-js/currencies'
35
+ *
36
+ * // Use with custom currency settings
37
+ * const customDecorator = priceDecorator({
38
+ * defaultCurrency: 'EUR',
39
+ * currencySettings: {
40
+ * EUR: {
41
+ * currencyBeforeAmount: true,
42
+ * currencyToken: '€',
43
+ * decimalCharacter: ',',
44
+ * groupingSeparator: '.',
45
+ * decimalPlaces: 2
46
+ * }
47
+ * }
48
+ * })
49
+ * const results = await search(
50
+ * { query: 'shoes' },
51
+ * { hitDecorators: [customDecorator] }
52
+ * )
53
+ * console.log(results.products.hits[0].priceText) // "€99,99"
54
+ * ```
14
55
  */
15
56
  export declare function priceDecorator(config?: Partial<CurrencyConfig>): (hit: SearchProduct) => Result;
@@ -27,11 +27,18 @@ export declare function withDefaultQuery(pageType: PageType, query: SearchQuery)
27
27
  size?: number;
28
28
  sort?: import('@nosto/nosto-js/client').InputSearchSort[];
29
29
  variationId?: string;
30
+ currency?: string;
30
31
  fields: string[];
31
32
  };
32
33
  accountId?: string | undefined;
33
34
  customRules?: import('@nosto/nosto-js/client').InputSearchRule[] | undefined;
34
35
  explain?: boolean | undefined;
36
+ popularSearches?: import('@nosto/nosto-js/client').InputSearchQuery["popularSearches"] & {
37
+ fields?: string[];
38
+ };
39
+ categories?: import('@nosto/nosto-js/client').InputSearchQuery["categories"] & {
40
+ fields?: string[];
41
+ };
35
42
  query?: string | undefined;
36
43
  redirect?: string | undefined;
37
44
  rules?: string[] | undefined;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const A=require("../../useActions-CY9uts_o.cjs"),P=require("../../logger-Boh_C6Bz.cjs"),h=require("../../useLoadMore-CBshMpps.cjs"),R=require("../../eventBusSubscribe-oONOUGH8.cjs"),l=require("preact/hooks"),C=require("../../useHistory-rc2PvSkv.cjs"),q=require("../../index.es-Dp6Iaxz3.cjs"),z=require("../../eventBusDispatch-BKQcSHAB.cjs"),F=require("../../parseNumber-FsZ8w61u.cjs");function O(e){const s=h.useNostoAppState(t=>t.response);return e&&Array.isArray(e)||e&&P.isPlainObject(e)?e:s}function X(e,s){const t=e.data?.filter(f=>f.selected).length??0,{active:o}={active:t>0,...s},[n,c]=l.useState(o),{toggleProductFilter:u}=A.useActions(),a=l.useCallback(()=>{c(!n)},[n]);return R.useEventBusSubscribe({event:"events/removeAllFilters",callback:()=>{c(!1)}}),{active:n,selectedFiltersCount:t,toggleActive:a,toggleProductFilter:u}}function E(){const{loading:e,facets:s}=h.useNostoAppState(t=>({loading:t.loading,facets:t.response?.products?.facets??[]}));return{loading:e,facets:s}}function j(e,s){const t=s-e;return!isNaN(t)&&t>0?new Array(s-e).fill(void 0).map((o,n)=>n+e):[]}function B(e){const{query:s,products:t}=h.useNostoAppState(o=>({query:o.query,products:o.response.products}));return l.useMemo(()=>{if(!t)return{totalPages:0,resultsFrom:0,resultsTo:0,pages:[]};const o=s.products?.from??0,n=e?.width??1/0,c=Math.max(Math.floor(n-1)/2,1),u=t.size>0?Math.floor(o/t.size)+1:1,a=t.size>0?Math.ceil(t.total/t.size):0,f=y=>y>=u-c&&y<=u+c,r=o+1,i=Math.min(o+t.total,t.total),d={from:o,page:u,current:!0},p=y=>({from:(y-1)*t.size,page:y,current:y===u}),S=u>1?p(u-1):void 0,v=u<a?p(u+1):void 0,m=u-c-1>1?p(1):void 0,g=u+c+1<a?p(a):void 0,b=j(1,a+1).filter(f).map(p);return!m&&b[0]?.page===2&&b.unshift(p(1)),!g&&b[b.length-1]?.page===a-1&&b.push(p(a)),{totalPages:a,resultsFrom:r,resultsTo:i,current:d,prev:S,next:v,first:m,last:g,pages:b}},[s,t,e?.width])}function V(){const[e,s]=l.useState([]),[t,o]=l.useState([]);return l.useEffect(()=>{q.s(async n=>{const{products:c,segments:u}=await n.getSearchSessionParams();s(u??[]),o(c?.personalizationBoost??[])})},[]),{segments:e,boost:t}}function D(){const{facets:e}=h.useNostoAppState(r=>({facets:r.response.products?.facets??[]})),{replaceFilter:s,toggleProductFilter:t}=A.useActions(),o=l.useCallback(r=>{const i=e?.find(d=>d.type==="stats"&&d.field===r);if(i&&"min"in i&&"max"in i)return i},[e]),n=l.useCallback(r=>e?.find(i=>i.field===r)?.name??r,[e]),c=l.useCallback(r=>"field"in r&&(r.value instanceof Array||r.range instanceof Array),[]),u=l.useCallback(r=>({...r,range:r.range?.map(i=>({gt:i.gt?Number(i.gt):i.gt,gte:i.gte?Number(i.gte):i.gte,lt:i.lt?Number(i.lt):i.lt,lte:i.lte?Number(i.lte):i.lte}))}),[]),a=l.useCallback(r=>(r.value??[]).map(d=>({value:d,field:r.field,name:n(r.field),filter:u(r),remove:()=>{t(r.field,d,!1)}})),[u,n,t]),f=l.useCallback(r=>(r.range??[]).map(d=>{const p=d.gte??d.gt??o(r.field)?.min,S=d.lte??d.lt??o(r.field)?.max;if(p!==void 0&&S!==void 0)return{value:`${p} - ${S}`,field:r.field,name:n(r.field),filter:u(r),remove:()=>{s(r.field,void 0)}}}).filter(Boolean),[u,o,n,s]);return{selectFilters:c,toValueFilter:a,toRangeFilter:f}}function L(){const{filter:e}=h.useNostoAppState(a=>({filter:a.query.products?.filter??[]})),{updateSearch:s}=A.useActions(),{selectFilters:t,toValueFilter:o,toRangeFilter:n}=D(),c=l.useMemo(()=>e?e.filter(t).flatMap(a=>"value"in a?o(a):"range"in a?n(a):[]).filter(Boolean):[],[e,t,n,o]),u=l.useCallback(()=>{s({products:{filter:[]}}),z.dispatchNostoEvent({event:"events/removeAllFilters",params:null})},[s]);return{filters:c,removeAll:u}}function T(e){const{replaceFilter:s}=A.useActions(),{query:t,products:o}=h.useNostoAppState(g=>({query:g.query,products:g.response.products})),n=o?.facets?.find(g=>g.id===e),c=t.products?.filter?.find(g=>g.field===n?.field),[u,a]=H(c),f=n&&"min"in n?Math.floor(n.min??0):0,r=n&&"max"in n?Math.ceil(n.max??0):0,i=l.useMemo(()=>[u??f,a??r],[u,a,f,r]),d=u!==void 0||a!==void 0,[p,S]=l.useState(d),v=l.useCallback(()=>{S(g=>!g)},[]),m=l.useCallback(([g,b])=>{if(!n)return;const y=U(g,b,f,r);s(n.field,y)},[f,r,s,n]);return R.useEventBusSubscribe({event:"events/removeAllFilters",callback:()=>{S(!1)}}),n?{min:f,max:r,range:i,updateRange:m,active:p,toggleActive:v}:$}const $={min:0,max:0,range:[0,0],active:!1,toggleActive:()=>{},updateRange:()=>{}};function H(e){const s=e?.range?.[0];return typeof s=="object"&&("gte"in s||"lte"in s)?[F.parseNumber(s.gte),F.parseNumber(s.lte)]:[void 0,void 0]}function U(e,s,t,o){const n=e!==void 0?Math.floor(e):void 0,c=s!==void 0?Math.ceil(s):void 0,u=n!==void 0,a=c!==void 0;if((t===n||!u)&&(o===c||!a))return;const f={};return u&&n!==t&&(f.gte=n.toString()),a&&c!==o&&(f.lte=c.toString()),Object.keys(f).length>0?f:void 0}function _(e,s){const{min:t,max:o,range:n,updateRange:c}=T(e),{filters:u}=L(),a=l.useMemo(()=>{const d=u.find(m=>m?.filter?.range);let p=null;if(d){const m=d.filter.range?.[0];p=[F.parseNumber(m?.gte),F.parseNumber(m?.lte)]}const S=[];let v=Math.floor(t/s)*s;for(;v<o;){const m=v+s,g=p&&p[0]===v&&p[1]===m;S.push({min:v,max:m,selected:g}),v=m}return S},[u,t,o,s]),f=l.useCallback(d=>{c([d,n[1]])},[n,c]),r=l.useCallback(d=>{c([n[0],d])},[n,c]),i=t!==n[0]||o!==n[1];return{min:t,max:o,range:n,updateRange:c,ranges:a,handleMinChange:f,handleMaxChange:r,isSelected:i}}function I(){const{products:e,keywords:s}=h.useNostoAppState(t=>({products:t.response.products??{hits:[],total:0},keywords:t.response.keywords??{hits:[],total:0}}));return{products:e,keywords:s}}function K(){const e=h.useNostoAppState(t=>t.query.products?.filter);return l.useMemo(()=>e?e.reduce((t,o)=>t+(Array.isArray(o.value)?o.value.length:1),0):0,[e])}const Z=5*60*1e3,k=new Map;function G(e){const[s,t]=l.useState({product:null,loading:!0,error:null});return l.useEffect(()=>{if(!e){t({product:null,loading:!1,error:"Product handle is required"});return}t(n=>({...n,loading:!0,error:null}));const o=W(e);if(o){t({product:o,loading:!1,error:null});return}Q(e).then(n=>{Y(e,n),t({product:n,loading:!1,error:null})}).catch(n=>{t({product:null,loading:!1,error:n.message||"Failed to fetch product"})})},[e]),s}function J(e){const s=window.Shopify?.routes?.root;return s?new URL(`${s}products/${e}`,window.location.href):e}async function Q(e){const s=await fetch(J(`/products/${e}.js`));if(!s.ok)throw new Error(`Failed to fetch product: ${s.status} ${s.statusText}`);return s.json()}function W(e){const s=k.get(e);return s?Date.now()-s.created>Z?(k.delete(e),null):s.product:null}function Y(e,s){k.set(e,{product:s,created:Date.now()})}function w(e){return e&&!Number.isNaN(e)?e:0}function ee(e,s){const{from:t,size:o,total:n}=h.useNostoAppState(r=>({from:w(r.query.products?.from??0),size:w(r.response?.products?.size??s),total:w(r.response?.products?.total??0)})),{updateSearch:c}=A.useActions(),u=t+o,a=l.useMemo(()=>[...e].reverse().filter(r=>r<n),[e,n]),f=l.useCallback(r=>{c({products:{size:F.parseNumber(r)}})},[c]);return{from:t,to:u,total:n,size:o,sizeOptions:a,handleSizeChange:f}}function te(e,s){return e.length!==s.length?!1:e.every(t=>s.find(o=>t.field===o.field&&t.order===o.order))}function se(e){const s=h.useNostoAppState(c=>c.query),{updateSearch:t}=A.useActions(),o=e.find(c=>te(c.value.sort,s.products?.sort||[]))?.id??e[0]?.id,n=l.useCallback(c=>{const u=e.find(a=>a.id===c);u&&t({products:{sort:u.value.sort}})},[e,t]);return{activeSort:o,setSort:n}}const N=window.SpeechRecognition||window.webkitSpeechRecognition,x=!!(N&&typeof N=="function");function ne(){return{listening:!1,startListening:()=>{},stopListening:()=>{}}}function oe({language:e="en-US",interimResults:s=!1,onResult:t,onError:o}={}){const[n,c]=l.useState(!1),u=l.useRef(null),a=l.useCallback(()=>{const r=new N;r.lang=e,r.interimResults=s,r.onstart=()=>c(!0),t&&(r.onresult=i=>{const{transcript:d}=i.results?.[0]?.[0];t(d)}),o&&(r.onerror=i=>o(i.error)),r.onend=()=>c(!1),u.current=r,r.start()},[e,s,o,t]),f=l.useCallback(()=>{u.current?.stop()},[u]);return{listening:n,startListening:a,stopListening:f}}const re=x?oe:ne;function ce(e,s){if(!e.length||!s.length)return[];const t=s.reduce((o,n)=>(o[n]={},o),{});return e.forEach(o=>{o.customFields?.forEach(({key:n,value:c})=>{const u=n.toLowerCase();s.includes(u)&&(t[u][c]=t[u][c]||[],t[u][c].push(o))})}),Object.entries(t).filter(o=>Object.keys(o[1]).length).map(([o,n])=>({field:o,options:Object.entries(n).map(([c,u])=>({value:c,skus:u,unavailable:!1,selected:!1}))}))}function ue(e,s){return e.length?e.map(({field:t,options:o})=>({field:t,options:o.map(n=>{const c=!n.skus?.some(a=>Object.entries(s).every(([f,r])=>f===t?!0:a.customFields?.find(d=>d.key.toLowerCase()===f)?.value===r)),u=s[t]===n.value;return{...n,unavailable:c,selected:u}})})):[]}const M=["4XS","3XS","2XS","XXS","XS","S","M","L","XL","XXL","2XL","XXXL","3XL","4XL"];function ie(e){if(M.includes(e))return 1e3+M.indexOf(e);const s=parseFloat(e);return isNaN(s)?e:s}function ae(e,s){return[...s].sort((t,o)=>{const[n,c]=[t.value,o.value].map(ie);return n<c?-1:1})}function le(e=[],s=[]){const[t,o]=l.useState({}),n=l.useMemo(()=>ce(e,s).map(({field:r,options:i})=>({field:r,options:ae(r,i)})),[e,s]),c=l.useMemo(()=>ue(n,t),[n,t]),u=l.useCallback((f,r)=>{o(i=>{const d={...i};return d[f]===r?delete d[f]:d[f]=r,d})},[]),a=l.useMemo(()=>Object.keys(t).filter(i=>t[i]).length===0?[]:c.filter(({field:i})=>t[i]).map(({field:i,options:d})=>{const p=t[i];return d.find(v=>v.value===p)?.skus??[]}).reduce((i,d)=>i.filter(p=>d.includes(p))),[c,t]);return{swatches:c,toggleOption:u,matchedSkus:a}}exports.useActions=A.useActions;exports.useLoadMore=h.useLoadMore;exports.useNostoAppState=h.useNostoAppState;exports.addToHistory=C.addToHistory;exports.getSavedHistory=C.getSavedHistory;exports.useHistory=C.useHistory;exports.speechToTextSupported=x;exports.useDecoratedSearchResults=O;exports.useFacet=X;exports.useFacets=E;exports.usePagination=B;exports.usePersonalization=V;exports.useProductFilters=L;exports.useRange=T;exports.useRangeSelector=_;exports.useResponse=I;exports.useSelectedFiltersCount=K;exports.useShopifyProduct=G;exports.useSizeOptions=ee;exports.useSort=se;exports.useSpeechToText=re;exports.useSwatches=le;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const A=require("../../useActions-CY9uts_o.cjs"),P=require("../../logger-Boh_C6Bz.cjs"),h=require("../../useLoadMore-CBshMpps.cjs"),R=require("../../eventBusSubscribe-oONOUGH8.cjs"),l=require("preact/hooks"),C=require("../../useHistory-rc2PvSkv.cjs"),q=require("../../index.es-Dp6Iaxz3.cjs"),z=require("../../eventBusDispatch-BKQcSHAB.cjs"),F=require("../../parseNumber-FsZ8w61u.cjs");function O(e){const s=h.useNostoAppState(t=>t.response);return e&&Array.isArray(e)||e&&P.isPlainObject(e)?e:s}function X(e,s){const t=e.data?.filter(f=>f.selected).length??0,{active:o}={active:t>0,...s},[n,c]=l.useState(o),{toggleProductFilter:u}=A.useActions(),a=l.useCallback(()=>{c(!n)},[n]);return R.useEventBusSubscribe({event:"events/removeAllFilters",callback:()=>{c(!1)}}),{active:n,selectedFiltersCount:t,toggleActive:a,toggleProductFilter:u}}function E(){const{loading:e,facets:s}=h.useNostoAppState(t=>({loading:t.loading,facets:t.response?.products?.facets??[]}));return{loading:e,facets:s}}function j(e,s){const t=s-e;return!isNaN(t)&&t>0?new Array(s-e).fill(void 0).map((o,n)=>n+e):[]}function B(e){const{query:s,products:t}=h.useNostoAppState(o=>({query:o.query,products:o.response.products}));return l.useMemo(()=>{if(!t)return{totalPages:0,resultsFrom:0,resultsTo:0,pages:[]};const o=s.products?.from??0,n=e?.width??1/0,c=Math.max(Math.floor(n-1)/2,1),u=t.size>0?Math.floor(o/t.size)+1:1,a=t.size>0?Math.ceil(t.total/t.size):0,f=y=>y>=u-c&&y<=u+c,r=o+1,i=Math.min(o+t.total,t.total),d={from:o,page:u,current:!0},p=y=>({from:(y-1)*t.size,page:y,current:y===u}),S=u>1?p(u-1):void 0,v=u<a?p(u+1):void 0,m=u-c-1>1?p(1):void 0,g=u+c+1<a?p(a):void 0,b=j(1,a+1).filter(f).map(p);return!m&&b[0]?.page===2&&b.unshift(p(1)),!g&&b[b.length-1]?.page===a-1&&b.push(p(a)),{totalPages:a,resultsFrom:r,resultsTo:i,current:d,prev:S,next:v,first:m,last:g,pages:b}},[s,t,e?.width])}function V(){const[e,s]=l.useState([]),[t,o]=l.useState([]);return l.useEffect(()=>{q.s(async n=>{const{products:c,segments:u}=await n.getSearchSessionParams();s(u??[]),o(c?.personalizationBoost??[])})},[]),{segments:e,boost:t}}function D(){const{facets:e}=h.useNostoAppState(r=>({facets:r.response.products?.facets??[]})),{replaceFilter:s,toggleProductFilter:t}=A.useActions(),o=l.useCallback(r=>{const i=e?.find(d=>d.type==="stats"&&d.field===r);if(i&&"min"in i&&"max"in i)return i},[e]),n=l.useCallback(r=>e?.find(i=>i.field===r)?.name??r,[e]),c=l.useCallback(r=>"field"in r&&(r.value instanceof Array||r.range instanceof Array),[]),u=l.useCallback(r=>({...r,range:r.range?.map(i=>({gt:i.gt?Number(i.gt):i.gt,gte:i.gte?Number(i.gte):i.gte,lt:i.lt?Number(i.lt):i.lt,lte:i.lte?Number(i.lte):i.lte}))}),[]),a=l.useCallback(r=>(r.value??[]).map(d=>({value:d,field:r.field,name:n(r.field),filter:u(r),remove:()=>{t(r.field,d,!1)}})),[u,n,t]),f=l.useCallback(r=>(r.range??[]).map(d=>{const p=d.gte??d.gt??o(r.field)?.min,S=d.lte??d.lt??o(r.field)?.max;if(p!==void 0&&S!==void 0)return{value:`${p} - ${S}`,field:r.field,name:n(r.field),filter:u(r),remove:()=>{s(r.field,void 0)}}}).filter(Boolean),[u,o,n,s]);return{selectFilters:c,toValueFilter:a,toRangeFilter:f}}function L(){const{filter:e}=h.useNostoAppState(a=>({filter:a.query.products?.filter??[]})),{updateSearch:s}=A.useActions(),{selectFilters:t,toValueFilter:o,toRangeFilter:n}=D(),c=l.useMemo(()=>e?e.filter(t).flatMap(a=>"value"in a?o(a):"range"in a?n(a):[]).filter(Boolean):[],[e,t,n,o]),u=l.useCallback(()=>{s({products:{filter:[]}}),z.dispatchNostoEvent({event:"events/removeAllFilters",params:null})},[s]);return{filters:c,removeAll:u}}function T(e){const{replaceFilter:s}=A.useActions(),{query:t,products:o}=h.useNostoAppState(g=>({query:g.query,products:g.response.products})),n=o?.facets?.find(g=>g.id===e),c=t.products?.filter?.find(g=>g.field===n?.field),[u,a]=H(c),f=n&&"min"in n?Math.floor(n.min??0):0,r=n&&"max"in n?Math.ceil(n.max??0):0,i=l.useMemo(()=>[u??f,a??r],[u,a,f,r]),d=u!==void 0||a!==void 0,[p,S]=l.useState(d),v=l.useCallback(()=>{S(g=>!g)},[]),m=l.useCallback(([g,b])=>{if(!n)return;const y=U(g,b,f,r);s(n.field,y)},[f,r,s,n]);return R.useEventBusSubscribe({event:"events/removeAllFilters",callback:()=>{S(!1)}}),n?{min:f,max:r,range:i,updateRange:m,active:p,toggleActive:v}:$}const $={min:0,max:0,range:[0,0],active:!1,toggleActive:()=>{},updateRange:()=>{}};function H(e){const s=e?.range?.[0];return typeof s=="object"&&("gte"in s||"lte"in s)?[F.parseNumber(s.gte),F.parseNumber(s.lte)]:[void 0,void 0]}function U(e,s,t,o){const n=e!==void 0?Math.floor(e):void 0,c=s!==void 0?Math.ceil(s):void 0,u=n!==void 0,a=c!==void 0;if((t===n||!u)&&(o===c||!a))return;const f={};return u&&n!==t&&(f.gte=n.toString()),a&&c!==o&&(f.lte=c.toString()),Object.keys(f).length>0?f:void 0}function _(e,s){const{min:t,max:o,range:n,updateRange:c}=T(e),{filters:u}=L(),a=l.useMemo(()=>{const d=u.find(m=>m?.filter?.range);let p=null;if(d){const m=d.filter.range?.[0];p=[F.parseNumber(m?.gte),F.parseNumber(m?.lte)]}const S=[];let v=Math.floor(t/s)*s;for(;v<o;){const m=v+s,g=p&&p[0]===v&&p[1]===m;S.push({min:v,max:m,selected:g}),v=m}return S},[u,t,o,s]),f=l.useCallback(d=>{c([d,n[1]])},[n,c]),r=l.useCallback(d=>{c([n[0],d])},[n,c]),i=t!==n[0]||o!==n[1];return{min:t,max:o,range:n,updateRange:c,ranges:a,handleMinChange:f,handleMaxChange:r,isSelected:i}}function I(){const{products:e,keywords:s,popularSearches:t,categories:o}=h.useNostoAppState(n=>({products:n.response.products??{hits:[],total:0},keywords:n.response.keywords??{hits:[],total:0},popularSearches:n.response.popularSearches??{hits:[],total:0},categories:n.response.categories??{hits:[],total:0}}));return{products:e,keywords:s,popularSearches:t,categories:o}}function K(){const e=h.useNostoAppState(t=>t.query.products?.filter);return l.useMemo(()=>e?e.reduce((t,o)=>t+(Array.isArray(o.value)?o.value.length:1),0):0,[e])}const Z=5*60*1e3,k=new Map;function G(e){const[s,t]=l.useState({product:null,loading:!0,error:null});return l.useEffect(()=>{if(!e){t({product:null,loading:!1,error:"Product handle is required"});return}t(n=>({...n,loading:!0,error:null}));const o=W(e);if(o){t({product:o,loading:!1,error:null});return}Q(e).then(n=>{Y(e,n),t({product:n,loading:!1,error:null})}).catch(n=>{t({product:null,loading:!1,error:n.message||"Failed to fetch product"})})},[e]),s}function J(e){const s=window.Shopify?.routes?.root;return s?new URL(`${s}products/${e}`,window.location.href):e}async function Q(e){const s=await fetch(J(`/products/${e}.js`));if(!s.ok)throw new Error(`Failed to fetch product: ${s.status} ${s.statusText}`);return s.json()}function W(e){const s=k.get(e);return s?Date.now()-s.created>Z?(k.delete(e),null):s.product:null}function Y(e,s){k.set(e,{product:s,created:Date.now()})}function w(e){return e&&!Number.isNaN(e)?e:0}function ee(e,s){const{from:t,size:o,total:n}=h.useNostoAppState(r=>({from:w(r.query.products?.from??0),size:w(r.response?.products?.size??s),total:w(r.response?.products?.total??0)})),{updateSearch:c}=A.useActions(),u=t+o,a=l.useMemo(()=>[...e].reverse().filter(r=>r<n),[e,n]),f=l.useCallback(r=>{c({products:{size:F.parseNumber(r)}})},[c]);return{from:t,to:u,total:n,size:o,sizeOptions:a,handleSizeChange:f}}function te(e,s){return e.length!==s.length?!1:e.every(t=>s.find(o=>t.field===o.field&&t.order===o.order))}function se(e){const s=h.useNostoAppState(c=>c.query),{updateSearch:t}=A.useActions(),o=e.find(c=>te(c.value.sort,s.products?.sort||[]))?.id??e[0]?.id,n=l.useCallback(c=>{const u=e.find(a=>a.id===c);u&&t({products:{sort:u.value.sort}})},[e,t]);return{activeSort:o,setSort:n}}const N=window.SpeechRecognition||window.webkitSpeechRecognition,x=!!(N&&typeof N=="function");function ne(){return{listening:!1,startListening:()=>{},stopListening:()=>{}}}function oe({language:e="en-US",interimResults:s=!1,onResult:t,onError:o}={}){const[n,c]=l.useState(!1),u=l.useRef(null),a=l.useCallback(()=>{const r=new N;r.lang=e,r.interimResults=s,r.onstart=()=>c(!0),t&&(r.onresult=i=>{const{transcript:d}=i.results?.[0]?.[0];t(d)}),o&&(r.onerror=i=>o(i.error)),r.onend=()=>c(!1),u.current=r,r.start()},[e,s,o,t]),f=l.useCallback(()=>{u.current?.stop()},[u]);return{listening:n,startListening:a,stopListening:f}}const re=x?oe:ne;function ce(e,s){if(!e.length||!s.length)return[];const t=s.reduce((o,n)=>(o[n]={},o),{});return e.forEach(o=>{o.customFields?.forEach(({key:n,value:c})=>{const u=n.toLowerCase();s.includes(u)&&(t[u][c]=t[u][c]||[],t[u][c].push(o))})}),Object.entries(t).filter(o=>Object.keys(o[1]).length).map(([o,n])=>({field:o,options:Object.entries(n).map(([c,u])=>({value:c,skus:u,unavailable:!1,selected:!1}))}))}function ue(e,s){return e.length?e.map(({field:t,options:o})=>({field:t,options:o.map(n=>{const c=!n.skus?.some(a=>Object.entries(s).every(([f,r])=>f===t?!0:a.customFields?.find(d=>d.key.toLowerCase()===f)?.value===r)),u=s[t]===n.value;return{...n,unavailable:c,selected:u}})})):[]}const M=["4XS","3XS","2XS","XXS","XS","S","M","L","XL","XXL","2XL","XXXL","3XL","4XL"];function ie(e){if(M.includes(e))return 1e3+M.indexOf(e);const s=parseFloat(e);return isNaN(s)?e:s}function ae(e,s){return[...s].sort((t,o)=>{const[n,c]=[t.value,o.value].map(ie);return n<c?-1:1})}function le(e=[],s=[]){const[t,o]=l.useState({}),n=l.useMemo(()=>ce(e,s).map(({field:r,options:i})=>({field:r,options:ae(r,i)})),[e,s]),c=l.useMemo(()=>ue(n,t),[n,t]),u=l.useCallback((f,r)=>{o(i=>{const d={...i};return d[f]===r?delete d[f]:d[f]=r,d})},[]),a=l.useMemo(()=>Object.keys(t).filter(i=>t[i]).length===0?[]:c.filter(({field:i})=>t[i]).map(({field:i,options:d})=>{const p=t[i];return d.find(v=>v.value===p)?.skus??[]}).reduce((i,d)=>i.filter(p=>d.includes(p))),[c,t]);return{swatches:c,toggleOption:u,matchedSkus:a}}exports.useActions=A.useActions;exports.useLoadMore=h.useLoadMore;exports.useNostoAppState=h.useNostoAppState;exports.addToHistory=C.addToHistory;exports.getSavedHistory=C.getSavedHistory;exports.useHistory=C.useHistory;exports.speechToTextSupported=x;exports.useDecoratedSearchResults=O;exports.useFacet=X;exports.useFacets=E;exports.usePagination=B;exports.usePersonalization=V;exports.useProductFilters=L;exports.useRange=T;exports.useRangeSelector=_;exports.useResponse=I;exports.useSelectedFiltersCount=K;exports.useShopifyProduct=G;exports.useSizeOptions=ee;exports.useSort=se;exports.useSpeechToText=re;exports.useSwatches=le;
@@ -3,7 +3,7 @@ import { a as k } from "../../logger-_fg_Za9y.js";
3
3
  import { a as S } from "../../useLoadMore-2OmOqicJ.js";
4
4
  import { u as Re } from "../../useLoadMore-2OmOqicJ.js";
5
5
  import { u as N } from "../../eventBusSubscribe-CzlS132j.js";
6
- import { useState as b, useCallback as g, useMemo as y, useEffect as P, useRef as X } from "preact/hooks";
6
+ import { useState as b, useCallback as p, useMemo as y, useEffect as P, useRef as X } from "preact/hooks";
7
7
  import { a as xe, g as Me, u as Te } from "../../useHistory-joVBx1r2.js";
8
8
  import { s as z } from "../../index.es-XNBESE3P.js";
9
9
  import { d as C } from "../../eventBusDispatch-DPR2Vwd4.js";
@@ -16,7 +16,7 @@ function le(e, n) {
16
16
  const t = e.data?.filter((d) => d.selected).length ?? 0, { active: o } = {
17
17
  active: t > 0,
18
18
  ...n
19
- }, [r, c] = b(o), { toggleProductFilter: i } = A(), u = g(() => {
19
+ }, [r, c] = b(o), { toggleProductFilter: i } = A(), u = p(() => {
20
20
  c(!r);
21
21
  }, [r]);
22
22
  return N({
@@ -72,8 +72,8 @@ function fe(e) {
72
72
  from: (w - 1) * t.size,
73
73
  page: w,
74
74
  current: w === i
75
- }), h = i > 1 ? f(i - 1) : void 0, v = i < u ? f(i + 1) : void 0, m = i - c - 1 > 1 ? f(1) : void 0, p = i + c + 1 < u ? f(u) : void 0, F = O(1, u + 1).filter(d).map(f);
76
- return !m && F[0]?.page === 2 && F.unshift(f(1)), !p && F[F.length - 1]?.page === u - 1 && F.push(f(u)), {
75
+ }), h = i > 1 ? f(i - 1) : void 0, v = i < u ? f(i + 1) : void 0, m = i - c - 1 > 1 ? f(1) : void 0, g = i + c + 1 < u ? f(u) : void 0, F = O(1, u + 1).filter(d).map(f);
76
+ return !m && F[0]?.page === 2 && F.unshift(f(1)), !g && F[F.length - 1]?.page === u - 1 && F.push(f(u)), {
77
77
  totalPages: u,
78
78
  resultsFrom: s,
79
79
  resultsTo: a,
@@ -81,12 +81,12 @@ function fe(e) {
81
81
  prev: h,
82
82
  next: v,
83
83
  first: m,
84
- last: p,
84
+ last: g,
85
85
  pages: F
86
86
  };
87
87
  }, [n, t, e?.width]);
88
88
  }
89
- function ge() {
89
+ function pe() {
90
90
  const [e, n] = b([]), [t, o] = b([]);
91
91
  return P(() => {
92
92
  z(async (r) => {
@@ -101,17 +101,17 @@ function ge() {
101
101
  function j() {
102
102
  const { facets: e } = S((s) => ({
103
103
  facets: s.response.products?.facets ?? []
104
- })), { replaceFilter: n, toggleProductFilter: t } = A(), o = g(
104
+ })), { replaceFilter: n, toggleProductFilter: t } = A(), o = p(
105
105
  (s) => {
106
106
  const a = e?.find((l) => l.type === "stats" && l.field === s);
107
107
  if (a && "min" in a && "max" in a)
108
108
  return a;
109
109
  },
110
110
  [e]
111
- ), r = g(
111
+ ), r = p(
112
112
  (s) => e?.find((a) => a.field === s)?.name ?? s,
113
113
  [e]
114
- ), c = g((s) => "field" in s && (s.value instanceof Array || s.range instanceof Array), []), i = g((s) => ({
114
+ ), c = p((s) => "field" in s && (s.value instanceof Array || s.range instanceof Array), []), i = p((s) => ({
115
115
  ...s,
116
116
  range: s.range?.map((a) => ({
117
117
  gt: a.gt ? Number(a.gt) : a.gt,
@@ -119,7 +119,7 @@ function j() {
119
119
  lt: a.lt ? Number(a.lt) : a.lt,
120
120
  lte: a.lte ? Number(a.lte) : a.lte
121
121
  }))
122
- }), []), u = g(
122
+ }), []), u = p(
123
123
  (s) => (s.value ?? []).map((l) => ({
124
124
  value: l,
125
125
  field: s.field,
@@ -130,7 +130,7 @@ function j() {
130
130
  }
131
131
  })),
132
132
  [i, r, t]
133
- ), d = g(
133
+ ), d = p(
134
134
  (s) => (s.range ?? []).map((l) => {
135
135
  const f = l.gte ?? l.gt ?? o(s.field)?.min, h = l.lte ?? l.lt ?? o(s.field)?.max;
136
136
  if (f !== void 0 && h !== void 0)
@@ -155,7 +155,7 @@ function j() {
155
155
  function q() {
156
156
  const { filter: e } = S((u) => ({
157
157
  filter: u.query.products?.filter ?? []
158
- })), { updateSearch: n } = A(), { selectFilters: t, toValueFilter: o, toRangeFilter: r } = j(), c = y(() => e ? e.filter(t).flatMap((u) => "value" in u ? o(u) : "range" in u ? r(u) : []).filter(Boolean) : [], [e, t, r, o]), i = g(() => {
158
+ })), { updateSearch: n } = A(), { selectFilters: t, toValueFilter: o, toRangeFilter: r } = j(), c = y(() => e ? e.filter(t).flatMap((u) => "value" in u ? o(u) : "range" in u ? r(u) : []).filter(Boolean) : [], [e, t, r, o]), i = p(() => {
159
159
  n({
160
160
  products: {
161
161
  filter: []
@@ -173,16 +173,16 @@ function q() {
173
173
  };
174
174
  }
175
175
  function E(e) {
176
- const { replaceFilter: n } = A(), { query: t, products: o } = S((p) => ({
177
- query: p.query,
178
- products: p.response.products
179
- })), r = o?.facets?.find((p) => p.id === e), c = t.products?.filter?.find((p) => p.field === r?.field), [i, u] = $(c), d = r && "min" in r ? Math.floor(r.min ?? 0) : 0, s = r && "max" in r ? Math.ceil(r.max ?? 0) : 0, a = y(() => [i ?? d, u ?? s], [i, u, d, s]), l = i !== void 0 || u !== void 0, [f, h] = b(l), v = g(() => {
180
- h((p) => !p);
181
- }, []), m = g(
182
- ([p, F]) => {
176
+ const { replaceFilter: n } = A(), { query: t, products: o } = S((g) => ({
177
+ query: g.query,
178
+ products: g.response.products
179
+ })), r = o?.facets?.find((g) => g.id === e), c = t.products?.filter?.find((g) => g.field === r?.field), [i, u] = $(c), d = r && "min" in r ? Math.floor(r.min ?? 0) : 0, s = r && "max" in r ? Math.ceil(r.max ?? 0) : 0, a = y(() => [i ?? d, u ?? s], [i, u, d, s]), l = i !== void 0 || u !== void 0, [f, h] = b(l), v = p(() => {
180
+ h((g) => !g);
181
+ }, []), m = p(
182
+ ([g, F]) => {
183
183
  if (!r)
184
184
  return;
185
- const w = U(p, F, d, s);
185
+ const w = U(g, F, d, s);
186
186
  n(r.field, w);
187
187
  },
188
188
  [d, s, n, r]
@@ -228,7 +228,7 @@ function U(e, n, t, o) {
228
228
  const d = {};
229
229
  return i && r !== t && (d.gte = r.toString()), u && c !== o && (d.lte = c.toString()), Object.keys(d).length > 0 ? d : void 0;
230
230
  }
231
- function pe(e, n) {
231
+ function ge(e, n) {
232
232
  const { min: t, max: o, range: r, updateRange: c } = E(e), { filters: i } = q(), u = y(() => {
233
233
  const l = i.find((m) => m?.filter?.range);
234
234
  let f = null;
@@ -239,20 +239,20 @@ function pe(e, n) {
239
239
  const h = [];
240
240
  let v = Math.floor(t / n) * n;
241
241
  for (; v < o; ) {
242
- const m = v + n, p = f && f[0] === v && f[1] === m;
242
+ const m = v + n, g = f && f[0] === v && f[1] === m;
243
243
  h.push({
244
244
  min: v,
245
245
  max: m,
246
- selected: p
246
+ selected: g
247
247
  }), v = m;
248
248
  }
249
249
  return h;
250
- }, [i, t, o, n]), d = g(
250
+ }, [i, t, o, n]), d = p(
251
251
  (l) => {
252
252
  c([l, r[1]]);
253
253
  },
254
254
  [r, c]
255
- ), s = g(
255
+ ), s = p(
256
256
  (l) => {
257
257
  c([r[0], l]);
258
258
  },
@@ -278,15 +278,21 @@ function pe(e, n) {
278
278
  };
279
279
  }
280
280
  function me() {
281
- const { products: e, keywords: n } = S((t) => ({
282
- products: t.response.products ?? { hits: [], total: 0 },
283
- keywords: t.response.keywords ?? { hits: [], total: 0 }
281
+ const { products: e, keywords: n, popularSearches: t, categories: o } = S((r) => ({
282
+ products: r.response.products ?? { hits: [], total: 0 },
283
+ keywords: r.response.keywords ?? { hits: [], total: 0 },
284
+ popularSearches: r.response.popularSearches ?? { hits: [], total: 0 },
285
+ categories: r.response.categories ?? { hits: [], total: 0 }
284
286
  }));
285
287
  return {
286
288
  /** Array of products */
287
289
  products: e,
288
290
  /** Array of keywords */
289
- keywords: n
291
+ keywords: n,
292
+ /** Array of popular searches */
293
+ popularSearches: t,
294
+ /** Array of categories */
295
+ categories: o
290
296
  };
291
297
  }
292
298
  function he() {
@@ -366,7 +372,7 @@ function Se(e, n) {
366
372
  from: L(s.query.products?.from ?? 0),
367
373
  size: L(s.response?.products?.size ?? n),
368
374
  total: L(s.response?.products?.total ?? 0)
369
- })), { updateSearch: c } = A(), i = t + o, u = y(() => [...e].reverse().filter((s) => s < r), [e, r]), d = g(
375
+ })), { updateSearch: c } = A(), i = t + o, u = y(() => [...e].reverse().filter((s) => s < r), [e, r]), d = p(
370
376
  (s) => {
371
377
  c({
372
378
  products: {
@@ -395,7 +401,7 @@ function K(e, n) {
395
401
  return e.length !== n.length ? !1 : e.every((t) => n.find((o) => t.field === o.field && t.order === o.order));
396
402
  }
397
403
  function ye(e) {
398
- const n = S((c) => c.query), { updateSearch: t } = A(), o = e.find((c) => K(c.value.sort, n.products?.sort || []))?.id ?? e[0]?.id, r = g(
404
+ const n = S((c) => c.query), { updateSearch: t } = A(), o = e.find((c) => K(c.value.sort, n.products?.sort || []))?.id ?? e[0]?.id, r = p(
399
405
  (c) => {
400
406
  const i = e.find((u) => u.id === c);
401
407
  i && t({
@@ -429,13 +435,13 @@ function J({
429
435
  onResult: t,
430
436
  onError: o
431
437
  } = {}) {
432
- const [r, c] = b(!1), i = X(null), u = g(() => {
438
+ const [r, c] = b(!1), i = X(null), u = p(() => {
433
439
  const s = new M();
434
440
  s.lang = e, s.interimResults = n, s.onstart = () => c(!0), t && (s.onresult = (a) => {
435
441
  const { transcript: l } = a.results?.[0]?.[0];
436
442
  t(l);
437
443
  }), o && (s.onerror = (a) => o(a.error)), s.onend = () => c(!1), i.current = s, s.start();
438
- }, [e, n, o, t]), d = g(() => {
444
+ }, [e, n, o, t]), d = p(() => {
439
445
  i.current?.stop();
440
446
  }, [i]);
441
447
  return {
@@ -493,7 +499,7 @@ function we(e = [], n = []) {
493
499
  const [t, o] = b({}), r = y(() => Q(e, n).map(({ field: s, options: a }) => ({
494
500
  field: s,
495
501
  options: ee(s, a)
496
- })), [e, n]), c = y(() => W(r, t), [r, t]), i = g((d, s) => {
502
+ })), [e, n]), c = y(() => W(r, t), [r, t]), i = p((d, s) => {
497
503
  o((a) => {
498
504
  const l = { ...a };
499
505
  return l[d] === s ? delete l[d] : l[d] = s, l;
@@ -516,10 +522,10 @@ export {
516
522
  Re as useLoadMore,
517
523
  S as useNostoAppState,
518
524
  fe as usePagination,
519
- ge as usePersonalization,
525
+ pe as usePersonalization,
520
526
  q as useProductFilters,
521
527
  E as useRange,
522
- pe as useRangeSelector,
528
+ ge as useRangeSelector,
523
529
  me as useResponse,
524
530
  he as useSelectedFiltersCount,
525
531
  ve as useShopifyProduct,
@@ -31,13 +31,6 @@ type RangeProps = [number | undefined, number | undefined];
31
31
  * ```
32
32
  */
33
33
  export declare function useRange(id: string): {
34
- readonly min: 0;
35
- readonly max: 0;
36
- readonly range: readonly [0, 0];
37
- readonly active: false;
38
- readonly toggleActive: () => void;
39
- readonly updateRange: () => void;
40
- } | {
41
34
  /** Min value */
42
35
  min: number;
43
36
  /** Max value */
@@ -80,9 +80,9 @@ export declare function useRangeSelector(id: string, rangeSize: number): {
80
80
  /** Maximum value */
81
81
  max: number;
82
82
  /** Range value */
83
- range: number[] | readonly [0, 0];
83
+ range: number[];
84
84
  /** Update range function */
85
- updateRange: (([from, to]: [number | undefined, number | undefined]) => void) | (() => void);
85
+ updateRange: ([from, to]: [number | undefined, number | undefined]) => void;
86
86
  /** Ranges */
87
87
  ranges: {
88
88
  min: number;
@@ -1,4 +1,4 @@
1
- import { SearchKeywords, SearchProducts } from '@nosto/nosto-js/client';
1
+ import { SearchCategories, SearchKeywords, SearchPopularSearches, SearchProducts } from '@nosto/nosto-js/client';
2
2
  /**
3
3
  * Preact hook that imports response data to the component.
4
4
  * @example
@@ -7,7 +7,7 @@ import { SearchKeywords, SearchProducts } from '@nosto/nosto-js/client';
7
7
  * import { defaultConfig } from "../config"
8
8
  *
9
9
  * export default () => {
10
- * const { products, keywords } = useResponse()
10
+ * const { products, keywords, popularSearches, categories } = useResponse()
11
11
  * return (
12
12
  * <div>
13
13
  * <div>
@@ -68,6 +68,34 @@ import { SearchKeywords, SearchProducts } from '@nosto/nosto-js/client';
68
68
  * <SubmitButton />
69
69
  * </div>
70
70
  * )}
71
+ * {popularSearches.hits.length > 0 && (
72
+ * <div>
73
+ * <div>
74
+ * Popular Searches
75
+ * </div>
76
+ * <div>
77
+ * {popularSearches.hits.map(hit => (
78
+ * <div key={hit.query}>
79
+ * {hit.query} ({hit.total} results)
80
+ * </div>
81
+ * ))}
82
+ * </div>
83
+ * </div>
84
+ * )}
85
+ * {categories.hits.length > 0 && (
86
+ * <div>
87
+ * <div>
88
+ * Categories
89
+ * </div>
90
+ * <div>
91
+ * {categories.hits.map(hit => (
92
+ * <div key={hit.externalId}>
93
+ * {hit.name}
94
+ * </div>
95
+ * ))}
96
+ * </div>
97
+ * </div>
98
+ * )}
71
99
  * </div>
72
100
  * </div>
73
101
  * )
@@ -79,4 +107,6 @@ import { SearchKeywords, SearchProducts } from '@nosto/nosto-js/client';
79
107
  export declare function useResponse(): {
80
108
  products: SearchProducts;
81
109
  keywords: SearchKeywords;
110
+ popularSearches: SearchPopularSearches;
111
+ categories: SearchCategories;
82
112
  };
@@ -5,5 +5,29 @@ export type Config = {
5
5
  /**
6
6
  * Replaces full size images with thumbnail sized versions.
7
7
  * Uses `shopifyThumbnailDecorator` and `nostoThumbnailDecorator` based on the platform.
8
+ *
9
+ * @param config - Configuration object specifying the desired thumbnail size.
10
+ * @returns A decorator function that transforms image URLs to thumbnail versions.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { search } from '@nosto/search-js'
15
+ * import { thumbnailDecorator } from '@nosto/search-js/thumbnails'
16
+ *
17
+ * // Use thumbnail decorator with search
18
+ * const results = await search(
19
+ * { query: 'shoes' },
20
+ * {
21
+ * hitDecorators: [
22
+ * thumbnailDecorator({ size: '200x200' })
23
+ * ]
24
+ * }
25
+ * )
26
+ * console.log(results.products.hits[0].thumb_url)
27
+ *
28
+ * // Available sizes: '100x100', '200x200', '400x400', '600x600', '800x800'
29
+ * const smallThumbs = thumbnailDecorator({ size: '100x100' })
30
+ * const largeThumbs = thumbnailDecorator({ size: '800x800' })
31
+ * ```
8
32
  */
9
33
  export declare function thumbnailDecorator({ size }: Config): (hit: import('@nosto/nosto-js/client').SearchProduct) => import('@nosto/nosto-js/client').SearchProduct;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nosto/search-js",
3
- "version": "3.21.1",
3
+ "version": "3.22.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "files": [
@@ -99,11 +99,11 @@
99
99
  "devDependencies": {
100
100
  "@commitlint/cli": "^20.1.0",
101
101
  "@commitlint/config-conventional": "^20.0.0",
102
- "@nosto/nosto-js": "^2.9.0",
102
+ "@nosto/nosto-js": "^2.9.2",
103
103
  "@testing-library/dom": "^10.4.1",
104
104
  "@types/dom-speech-recognition": "^0.0.7",
105
105
  "@types/eslint-config-prettier": "^6.11.3",
106
- "@types/node": "^24.9.0",
106
+ "@types/node": "^24.9.1",
107
107
  "@vitest/coverage-v8": "^3.2.4",
108
108
  "concurrently": "^9.2.1",
109
109
  "copyfiles": "^2.4.1",
@@ -112,7 +112,7 @@
112
112
  "eslint-plugin-barrel-files": "^3.0.1",
113
113
  "eslint-plugin-prettier": "^5.5.4",
114
114
  "eslint-plugin-react": "^7.37.5",
115
- "eslint-plugin-react-hooks": "^7.0.0",
115
+ "eslint-plugin-react-hooks": "^7.0.1",
116
116
  "eslint-plugin-simple-import-sort": "^12.1.1",
117
117
  "eslint-plugin-unused-imports": "^4.3.0",
118
118
  "husky": "^9.1.7",
@@ -122,7 +122,7 @@
122
122
  "typedoc": "^0.28.14",
123
123
  "typescript": "^5.9.3",
124
124
  "typescript-eslint": "^8.46.2",
125
- "vite": "^7.1.11",
125
+ "vite": "^7.1.12",
126
126
  "vite-plugin-dts": "^4.5.4",
127
127
  "vitest": "^3.1.3"
128
128
  },