@nosto/search-js 1.5.1 → 1.7.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.
@@ -0,0 +1,51 @@
1
+ import { ComponentChildren, ComponentType } from 'preact';
2
+ import { InfiniteScrollWithObserver } from './InfiniteScrollWithObserver';
3
+ /**
4
+ * Infinite scroll component props
5
+ * @param children - The children to render
6
+ * @param loadMoreComponent - The component to render when more results are available
7
+ * @param pageSize - The page size to use when loading more results
8
+ */
9
+ export interface InfiniteScrollProps {
10
+ children: ComponentChildren;
11
+ loadMoreComponent?: ComponentType<{
12
+ pageSize?: number;
13
+ }>;
14
+ pageSize?: number;
15
+ }
16
+ /**
17
+ * Infinite scroll component that loads more results when user scrolls to the end of the page.
18
+ * If the browser does not support IntersectionObserver, a load more button is shown instead.
19
+ * @param props
20
+ * @example
21
+ * ```jsx
22
+ * import { InfiniteScroll } from "@nosto/search-js/preact"
23
+ *
24
+ * <InfiniteScroll pageSize={defaultConfig.pageSize}>
25
+ * {products?.hits?.map(hit => <Product hit={hit} />)}
26
+ * </InfiniteScroll>
27
+ *
28
+ * <InfiniteScroll pageSize={defaultConfig.pageSize}>
29
+ * <Products />
30
+ * </InfiniteScroll>
31
+ *
32
+ * // With custom load more button
33
+ * function LoadMoreButton({ pageSize }) {
34
+ * const { loadMore } = useLoadMore(pageSize)
35
+ *
36
+ * return (
37
+ * <button
38
+ * onClick={loadMore}
39
+ * >
40
+ * More results
41
+ * </button>
42
+ * )
43
+ * }
44
+ *
45
+ * <InfiniteScroll loadMoreComponent={LoadMoreButton} pageSize={defaultConfig.pageSize}>
46
+ * {products?.hits?.map(hit => <Product hit={hit} />)}
47
+ * </InfiniteScroll>
48
+ * ```
49
+ * @group Components
50
+ */
51
+ export declare const InfiniteScroll: typeof InfiniteScrollWithObserver;
@@ -0,0 +1,6 @@
1
+ import { InfiniteScrollProps } from './InfiniteScroll';
2
+ /**
3
+ * Infinite scroll component that loads more results when user clicks a link.
4
+ * @group Components
5
+ */
6
+ export declare function InfiniteScrollWithLink({ children, loadMoreComponent: LoadMore, pageSize }: InfiniteScrollProps): import("preact").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { JSX } from 'preact';
2
+ import { InfiniteScrollProps } from './InfiniteScroll';
3
+ /**
4
+ * Infinite scroll component that loads more results when user scrolls to the end of the page.
5
+ * @group Components
6
+ */
7
+ export declare function InfiniteScrollWithObserver({ children, pageSize }: InfiniteScrollProps): JSX.Element;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Load more link component
3
+ * @group Components
4
+ */
5
+ export declare function LoadMoreLink({ pageSize }: {
6
+ pageSize?: number;
7
+ }): import("preact").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { SearchQuery, SearchResult } from '@nosto/nosto-js/client';
2
+ /**
3
+ * Function to check if there are more results to fetch.
4
+ */
5
+ export declare function hasMoreResults(query: SearchQuery, result: SearchResult): boolean;
6
+ /**
7
+ * Function to check if IntersectionObserver is supported in the browser.
8
+ */
9
+ export declare function intersectionObserverSupported(): boolean;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Function to calculate the new query values for loading more products
3
+ */
4
+ export declare function getNextPageQuery({ from, size, pageSize }: {
5
+ from: number;
6
+ size: number;
7
+ pageSize: number;
8
+ }): {
9
+ products: {
10
+ from: number;
11
+ size?: undefined;
12
+ };
13
+ } | {
14
+ products: {
15
+ size: number;
16
+ from?: undefined;
17
+ };
18
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Hook for loading more results by specified pageSize value
3
+ * @example
4
+ * ```jsx
5
+ * import { useLoadMore } from '@nosto/search-js/preact'
6
+ *
7
+ * function LoadMoreButton({ pageSize }) {
8
+ * const { loadMore } = useLoadMore(pageSize)
9
+ *
10
+ * return (
11
+ * <a
12
+ * onClick={loadMore}
13
+ * >
14
+ * More results
15
+ * </a>
16
+ * )
17
+ * }
18
+ * ```
19
+ * @param pageSize - number of additional products to load (default=24)
20
+ * @group Hooks
21
+ */
22
+ export declare function useLoadMore(pageSize?: number): {
23
+ loadMore: () => Promise<void>;
24
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Pagination page type.
3
+ */
4
+ export type Page = {
5
+ from: number;
6
+ page: number;
7
+ current: boolean;
8
+ };
9
+ /**
10
+ * Preact hook that import pagination state to the component.
11
+ *
12
+ * **Should always be at the top of the component!**
13
+ *
14
+ * @example
15
+ * ```jsx
16
+ * import { usePagination } from '@nosto/search-js/preact'
17
+ *
18
+ * export default () => {
19
+ * const { pages } = usePagination()
20
+ *
21
+ * return pages.map((page) => <li>
22
+ * {page.current ? <span>{page.page}</span> : <a>
23
+ * {page.page}
24
+ * </a>}
25
+ * </li>)
26
+ * }
27
+ * ```
28
+ *
29
+ * @group Hooks
30
+ */
31
+ export declare function usePagination(options?: {
32
+ width?: number;
33
+ }): {
34
+ totalPages: number;
35
+ resultsFrom: number;
36
+ resultsTo: number;
37
+ current?: Page;
38
+ prev?: Page;
39
+ next?: Page;
40
+ first?: Page;
41
+ last?: Page;
42
+ pages: Page[];
43
+ };
@@ -0,0 +1,43 @@
1
+ import { s } from "./index.es-B8mbAxS4.js";
2
+ function a(t, r) {
3
+ if (!t.products || !(r != null && r.length))
4
+ return t;
5
+ const e = (i) => r.reduce((u, n) => n(u), i);
6
+ return {
7
+ ...t,
8
+ products: {
9
+ ...t.products,
10
+ hits: t.products.hits.map(e)
11
+ }
12
+ };
13
+ }
14
+ function o(t) {
15
+ return new Promise((r) => setTimeout(r, t));
16
+ }
17
+ async function f(t, r, e) {
18
+ const { maxRetries: i = 0, retryInterval: u = 1e3 } = e;
19
+ let n = 0;
20
+ for (; ; )
21
+ try {
22
+ return await t.search(r, e);
23
+ } catch (c) {
24
+ if (!h(c))
25
+ throw console.info("Skipping retry logic for", c), c;
26
+ if (n >= i)
27
+ throw c;
28
+ n++, await o(u);
29
+ }
30
+ }
31
+ function h(t) {
32
+ return !t || typeof t != "object" ? !1 : !("status" in t) || l(t.status);
33
+ }
34
+ function l(t) {
35
+ return typeof t == "number" && (t < 400 || t >= 500);
36
+ }
37
+ async function p(t, r = {}) {
38
+ const { hitDecorators: e, ...i } = r, u = await new Promise(s), n = await f(u, t, i);
39
+ return a(n, e);
40
+ }
41
+ export {
42
+ p as s
43
+ };
@@ -0,0 +1 @@
1
+ "use strict";const s=require("./index.es-DlUp67LT.cjs");function a(t,e){if(!t.products||!(e!=null&&e.length))return t;const r=i=>e.reduce((u,n)=>n(u),i);return{...t,products:{...t.products,hits:t.products.hits.map(r)}}}function o(t){return new Promise(e=>setTimeout(e,t))}async function f(t,e,r){const{maxRetries:i=0,retryInterval:u=1e3}=r;let n=0;for(;;)try{return await t.search(e,r)}catch(c){if(!h(c))throw console.info("Skipping retry logic for",c),c;if(n>=i)throw c;n++,await o(u)}}function h(t){return!t||typeof t!="object"?!1:!("status"in t)||l(t.status)}function l(t){return typeof t=="number"&&(t<400||t>=500)}async function y(t,e={}){const{hitDecorators:r,...i}=e,u=await new Promise(s.s),n=await f(u,t,i);return a(n,r)}exports.search=y;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("../index.es-D_7T9vdv.cjs");function s({size:e,productId:a,hash:u}){const i=p.i();if(!i)throw new Error("Client script settings are not yet available");return`https://${i.thumbnailHost}/${i.account}/${e}/${a}/${u}/A`}function l({size:e}){function a(c,r){if(r)return s({size:e,productId:c,hash:r})}function u(c,r){if(r)return r.map(t=>({...t,imageUrl:a(c,t.imageHash)??t.imageUrl}))}function i(c,r,t){if(!t)return r;if(r)return t.map(n=>s({size:e,productId:c,hash:n}))}return function(r){const t=r.productId;return t?{...r,imageUrl:a(t,r.imageHash)??r.imageUrl,thumbUrl:a(t,r.thumbHash)??r.thumbUrl,skus:u(t,r.skus),alternateImageUrls:i(t,r.alternateImageUrls,r.alternateImageHashes)}:r}}const g=/cdn\.shopify\.com/,m={1:"170x170_crop_center",2:"100x100_crop_center",3:"90x70_crop_center",4:"50x50_crop_center",5:"30x30_crop_center",6:"100x140_crop_center",7:"200x200_crop_center",8:"400x400",9:"750x750"};function U(e){return e in m}function f({size:e,fallback:a=u=>u}){if(e==="orig")return a;const u=m[e]||e;function i(n){return n?new URL(n).hostname.match(g):!1}function c(n){return n?i(n)?n.replace(/(\.jpg|\.png|\.jpeg|\.gif|\.webp)/,`_${u}$1`):n:""}function r(n){if(n)return n.map(o=>({...o,imageUrl:c(o.imageUrl)}))}function t(n){if(n)return n.map(o=>c(o))}return function(o){return i(o.imageUrl)?{...o,imageUrl:c(o.imageUrl),thumbUrl:c(o.thumbUrl),skus:r(o.skus),alternateImageUrls:t(o.alternateImageUrls)}:a(o)}}function d(){var e;return(e=window.Nosto)==null?void 0:e.shopifyScript}function b({size:e}){const a=l({size:e});return d()&&U(e)?f({size:e,fallback:a}):a}exports.generateThumbnailUrl=s;exports.nostoThumbnailDecorator=l;exports.shopifyThumbnailDecorator=f;exports.thumbnailDecorator=b;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("../index.es-DlUp67LT.cjs");function s({size:e,productId:a,hash:u}){const i=p.i();if(!i)throw new Error("Client script settings are not yet available");return`https://${i.thumbnailHost}/${i.account}/${e}/${a}/${u}/A`}function l({size:e}){function a(c,r){if(r)return s({size:e,productId:c,hash:r})}function u(c,r){if(r)return r.map(t=>({...t,imageUrl:a(c,t.imageHash)??t.imageUrl}))}function i(c,r,t){if(!t)return r;if(r)return t.map(n=>s({size:e,productId:c,hash:n}))}return function(r){const t=r.productId;return t?{...r,imageUrl:a(t,r.imageHash)??r.imageUrl,thumbUrl:a(t,r.thumbHash)??r.thumbUrl,skus:u(t,r.skus),alternateImageUrls:i(t,r.alternateImageUrls,r.alternateImageHashes)}:r}}const g=/cdn\.shopify\.com/,m={1:"170x170_crop_center",2:"100x100_crop_center",3:"90x70_crop_center",4:"50x50_crop_center",5:"30x30_crop_center",6:"100x140_crop_center",7:"200x200_crop_center",8:"400x400",9:"750x750"};function U(e){return e in m}function f({size:e,fallback:a=u=>u}){if(e==="orig")return a;const u=m[e]||e;function i(n){return n?new URL(n).hostname.match(g):!1}function c(n){return n?i(n)?n.replace(/(\.jpg|\.png|\.jpeg|\.gif|\.webp)/,`_${u}$1`):n:""}function r(n){if(n)return n.map(o=>({...o,imageUrl:c(o.imageUrl)}))}function t(n){if(n)return n.map(o=>c(o))}return function(o){return i(o.imageUrl)?{...o,imageUrl:c(o.imageUrl),thumbUrl:c(o.thumbUrl),skus:r(o.skus),alternateImageUrls:t(o.alternateImageUrls)}:a(o)}}function d(){var e;return(e=window.Nosto)==null?void 0:e.shopifyScript}function b({size:e}){const a=l({size:e});return d()&&U(e)?f({size:e,fallback:a}):a}exports.generateThumbnailUrl=s;exports.nostoThumbnailDecorator=l;exports.shopifyThumbnailDecorator=f;exports.thumbnailDecorator=b;
@@ -1,4 +1,4 @@
1
- import { i as m } from "../index.es-Bcd5IQh9.js";
1
+ import { i as m } from "../index.es-B8mbAxS4.js";
2
2
  function s({ size: e, productId: a, hash: i }) {
3
3
  const u = m();
4
4
  if (!u)
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Check if the current user agent is a bot
3
+ */
4
+ export declare function isBot(): boolean;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Pick properties from an object
3
+ * @param obj - The object to pick properties from
4
+ * @param props - The properties to pick
5
+ */
6
+ export declare function pick<T extends object, K extends keyof T>(obj: T, ...props: K[]): Pick<T, K>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Generate an array of numbers from start to exlusive end
3
+ * @param start number of array range start
4
+ * @param end number of array range end
5
+ * @returns an array of numbers from start to exlusive end
6
+ * @example
7
+ * ```ts
8
+ * range(1, 5) // [1, 2, 3, 4]
9
+ * ```
10
+ */
11
+ export declare function range(start: number, end: number): number[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nosto/search-js",
3
- "version": "1.5.1",
3
+ "version": "1.7.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "files": [
@@ -53,28 +53,29 @@
53
53
  "install:dev": "(cd dev/preact && npm i --ignore-scripts)"
54
54
  },
55
55
  "devDependencies": {
56
- "@nosto/nosto-js": "^1.5.0",
56
+ "@nosto/nosto-js": "^1.7.0",
57
57
  "@testing-library/dom": "^10.4.0",
58
58
  "@types/eslint-config-prettier": "^6.11.3",
59
- "@types/node": "^22.13.10",
59
+ "@types/node": "^22.13.13",
60
60
  "concurrently": "^9.1.2",
61
61
  "copyfiles": "^2.4.1",
62
- "eslint": "^9.22.0",
62
+ "eslint": "^9.23.0",
63
63
  "eslint-config-prettier": "^10.1.1",
64
64
  "eslint-plugin-barrel-files": "^3.0.1",
65
- "eslint-plugin-prettier": "^5.2.3",
66
- "eslint-plugin-simple-import-sort": "^12.1.1",
67
- "eslint-plugin-unused-imports": "^4.1.4",
65
+ "eslint-plugin-prettier": "^5.2.4",
68
66
  "eslint-plugin-react": "^7.37.4",
69
67
  "eslint-plugin-react-hooks": "^5.2.0",
68
+ "eslint-plugin-simple-import-sort": "^12.1.1",
69
+ "eslint-plugin-unused-imports": "^4.1.4",
70
+ "isbot": "^5.1.25",
70
71
  "jsdom": "^26.0.0",
71
72
  "prettier": "^3.5.3",
72
73
  "typedoc": "^0.27.9",
73
74
  "typescript": "^5.8.2",
74
- "typescript-eslint": "^8.26.0",
75
- "vite": "^6.2.1",
75
+ "typescript-eslint": "^8.28.0",
76
+ "vite": "^6.2.3",
76
77
  "vite-plugin-dts": "^4.5.3",
77
- "vitest": "^3.0.8"
78
+ "vitest": "^3.0.9"
78
79
  },
79
80
  "publishConfig": {
80
81
  "access": "public"
@@ -1,20 +0,0 @@
1
- function o() {
2
- window.nostojs = window.nostojs ?? function(n) {
3
- (window.nostojs.q = window.nostojs.q ?? []).push(n);
4
- };
5
- }
6
- async function i(n) {
7
- return window.nostojs(n);
8
- }
9
- let t = null;
10
- typeof window < "u" && (o(), i((n) => {
11
- t = n.internal.getSettings();
12
- }));
13
- function s() {
14
- return t;
15
- }
16
- typeof window < "u" && o();
17
- export {
18
- s as i,
19
- i as s
20
- };
@@ -1 +0,0 @@
1
- "use strict";function o(){window.nostojs=window.nostojs??function(n){(window.nostojs.q=window.nostojs.q??[]).push(n)}}async function t(n){return window.nostojs(n)}let s=null;typeof window<"u"&&(o(),t(n=>{s=n.internal.getSettings()}));function i(){return s}typeof window<"u"&&o();exports.i=i;exports.s=t;