@nosto/nosto-react 2.7.0 → 2.7.1

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/README.md CHANGED
@@ -9,7 +9,7 @@ Nosto React is a React component library to make it even easier to implement Nos
9
9
 
10
10
  The library provides you everything to get started with personalisation on your React site. It's dead simple, and beginner friendly.
11
11
 
12
- ### Why?
12
+ ## Why?
13
13
 
14
14
  You should be using Nosto React if you want to:
15
15
 
@@ -17,269 +17,14 @@ You should be using Nosto React if you want to:
17
17
  - Customize your components at will and with ease
18
18
  - Follow React principles
19
19
 
20
- ## Feature list
20
+ ## Installation
21
21
 
22
- Our React component library includes the following features:
22
+ yarn add @nosto/nosto-react
23
23
 
24
- - Recommendations, including client-side rendering
25
- - Onsite content personalisation
26
- - Dynamic bundles
27
- - Debug toolbar\* (excluding advanced use cases)
28
- - Pop-ups & personalised emails
29
- - A/B testing
30
- - Segmentation and Insights
31
- - Analytics
32
- - Search\*\* (when implemented via our code editor)
24
+ npm install @nosto/nosto-react
33
25
 
34
- _\*Note: Our React component library currently does not support advanced use cases of the debug toolbar, but we are constantly working to improve our library and provide you with the best possible integration options. We support most use-cases with page tagging and debug workflows._
26
+ ## Documentation
35
27
 
36
- _\*\*Note: The search feature is available when implemented via our code editor._
28
+ Read [Nosto Techdocs](https://docs.nosto.com/techdocs/apis/frontend/oss/react-support) for more information on how to use the library.
37
29
 
38
- ### Additional features
39
-
40
- - ✓ Lightweight. Almost zero bloat.
41
- - ✓ Full support for the Facebook Pixel and Google Analytics.
42
- - ✓ Full support for leveraging overlays.
43
- - ✓ Full support for the JS API.
44
- - ✓ Full support for placements.
45
-
46
- ### Building
47
-
48
- #### Required versions
49
-
50
- - npm: 10.9.0
51
- - node: v22.12.0
52
-
53
- ### Installation
54
-
55
- ##### Yarn:
56
-
57
- yarn add @nosto/nosto-react
58
-
59
- ##### NPM:
60
-
61
- npm install @nosto/nosto-react
62
-
63
- ### Getting Started
64
-
65
- ##### The root widget
66
-
67
- There’s one very specific widget in Nosto React and it is the `NostoProvider` one.
68
-
69
- This widget is what we call the Nosto root widget, which is responsible for adding the actual Nosto script and the JS API stub. This widget wraps all other React Nosto widgets. Here’s how:
70
-
71
- ```jsx
72
- import { NostoProvider } from "@nosto/nosto-react"
73
- <NostoProvider account="your-nosto-account-id" recommendationComponent={<NostoSlot />}>
74
- <App />
75
- </NostoProvider>
76
- ```
77
-
78
- **Note:** the component also accepts a prop to configure the host `host="connect.nosto.com"`. In advanced use-cases, the need to configure the host may surface.
79
-
80
- #### Client side rendering for recommendations
81
-
82
- In order to implement client-side rendering, the <NostoProvider> requires a designated component to render the recommendations provided by Nosto. This component should be capable of processing the JSON response received from our backend. Notice the `recommendationComponent={<NostoSlot />}` prop passed to `<NostoProvider>` above.
83
-
84
- Learn more [here](https://github.com/Nosto/shopify-hydrogen/blob/main/README.md#client-side-rendering-for-recommendations) and see a [live example](https://github.com/Nosto/shopify-hydrogen-demo) on our demo store.
85
-
86
- ##### Understanding Placements
87
-
88
- Nosto React has a special component called `NostoPlacement`. The component is a simply a <u>hidden</u> `<div>` placeholder into which Nosto injects recommendations or personalises the content between the tags.
89
-
90
- We recommend adding as many placements across your views as needed as these are hidden and only populated when a corresponding campaign (targeting that placement) is configured.
91
-
92
- ##### Managing the session
93
-
94
- Nosto React requires that you pass it the details of current cart contents and the details of the currently logged-in customer, if any, on every route change. This makes it easier to add attribution.
95
-
96
- The `NostoSession` component makes it very easy to keep the session up to date so long as the cart and the customer are provided.
97
-
98
- The `cart` prop requires a value that adheres to the type `Cart`, while the `customer` prop requires a value that adheres to the type `Customer`.
99
-
100
- ```jsx
101
- import { NostoSession } from "@nosto/nosto-react"
102
- <>
103
- <Meta />
104
- <header>
105
- <MainMenu />
106
- <NostoSession cart={currentCart} customer={currentUser} />
107
- </header>
108
- <Routes />
109
- <Footer />
110
- </>
111
- ```
112
-
113
- ### Adding Personalisation
114
-
115
- Nosto React ships with canned components for the different page types. Each component contains all lifecycle methods to dispatch the necessary events.
116
-
117
- ##### Personalising your home page
118
-
119
- The `NostoHome` component must be used to personalise the home page. The component does not require any props.
120
-
121
- By default, your account, when created, has <u>four</u> front-page placements named `frontpage-nosto-1`, `frontpage-nosto-2`, `frontpage-nosto-3` and `frontpage-nosto-4`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
122
-
123
- The `<NostoHome \>` component needs to be added after the placements. Content and recommendations will be rendered through this component.
124
-
125
- ```jsx
126
- import { NostoHome, NostoPlacement } from "@nosto/nosto-react"
127
- <div className="front-page">
128
- ... ... ...
129
- <NostoPlacement id="frontpage-nosto-1" />
130
- <NostoPlacement id="frontpage-nosto-2" />
131
- <NostoPlacement id="frontpage-nosto-3" />
132
- <NostoPlacement id="frontpage-nosto-4" />
133
- <NostoHome />
134
- </div>
135
- ```
136
-
137
- ##### Personalising your product pages
138
-
139
- The `NostoProduct` component must be used to personalise the product page. The component requires that you provide it the identifier of the current product being viewed.
140
-
141
- By default, your account, when created, has <u>three</u> product-page placements named `productpage-nosto-1`, `productpage-nosto-2` and `productpage-nosto-3`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
142
-
143
- The `<NostoProduct \>` component needs to be added after the placements. Content and recommendations will be rendered through this component. Pass in the product ID via the `product` prop to pass this information back to Nosto.
144
-
145
- ```jsx
146
- import { NostoPlacement, NostoProduct } from "@nosto/nosto-react"
147
- <div className="product-page">
148
- ... ... ...
149
- <NostoPlacement id="productpage-nosto-1" />
150
- <NostoPlacement id="productpage-nosto-2" />
151
- <NostoPlacement id="productpage-nosto-3" />
152
- <NostoProduct product={product.id} />
153
- </div>
154
- ```
155
-
156
- ##### Personalising your search result pages
157
-
158
- You can personalise your search pages by using the `NostoSearch` component. The component requires that you provide it the current search term.
159
-
160
- By default, your account, when created, has <u>two</u> search-page placements named `searchpage-nosto-1` and `searchpage-nosto-2`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
161
-
162
- ```jsx
163
- import { NostoPlacement, NostoSearch } from "@nosto/nosto-react"
164
- <div className="search-page">
165
- ... ... ...
166
- <NostoPlacement id="searchpage-nosto-1" />
167
- <NostoPlacement id="searchpage-nosto-2" />
168
- <NostoSearch query={search} />
169
- </div>
170
- ```
171
-
172
- **Note:** Do not encode the search term in any way. It should be provided an unencoded string. A query for "black shoes" must be provided as-is and not as "black+shoes". Doing so will lead to invalid results.
173
-
174
- ##### Personalising your category list pages
175
-
176
- You can personalise your category and collection pages by using the `NostoCategory` component. The component requires that you provide it the the slash-delimited slug representation of the current category.
177
-
178
- By default, your account, when created, has <u>two</u> category placements named `categorypage-nosto-1` and `categorypage-nosto-2`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
179
-
180
- ```jsx
181
- import { NostoCategory, NostoPlacement } from "@nosto/nosto-react"
182
- <div className="category-page">
183
- ... ... ...
184
- <NostoPlacement id="categorypage-nosto-1" />
185
- <NostoPlacement id="categorypage-nosto-2" />
186
- <NostoCategory category={category.name} />
187
- </div>
188
- ```
189
-
190
- **Note:** Be sure to pass in the correct category representation. If the category being viewed is Mens >> Jackets, you must provide the name as `/Mens/Jackets` . You must ensure that the category path provided here matches that of the categories tagged in your products.
191
-
192
- ##### Personalising your cart checkout pages
193
-
194
- You can personalise your cart and checkout pages by using the `NostoCheckout` component. The component does not require any props.
195
-
196
- By default, your account, when created, has <u>two</u> cart-page placements named `categorypage-nosto-1` and `categorypage-nosto-2`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
197
-
198
- ```jsx
199
- import { NostoCheckout, NostoPlacement } from "@nosto/nosto-react"
200
- <div className="checkout-page">
201
- ... ... ...
202
- <NostoPlacement id="checkout-nosto-1" />
203
- <NostoPlacement id="checkout-nosto-2" />
204
- <NostoCheckout />
205
- </div>
206
- ```
207
-
208
- ##### Personalising your 404 error pages
209
-
210
- You can personalise not found pages by using the `Nosto404` component. The component does not require any props.
211
-
212
- By default, your account, when created, has three 404-page placements named `notfound-nosto-1`, `notfound-nosto-2` and `notfound-nosto-2`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
213
-
214
- ```jsx
215
- import { Nosto404, NostoPlacement } from "@nosto/nosto-react"
216
- <div className="notfound-page">
217
- ... ... ...
218
- <NostoPlacement id="notfound-nosto-1" />
219
- <NostoPlacement id="notfound-nosto-2" />
220
- <NostoPlacement id="notfound-nosto-3" />
221
- <Nosto404 />
222
- </div>
223
- ```
224
-
225
- ##### Personalising your miscellaneous pages
226
-
227
- You can personalise your miscellaneous pages by using the `NostoOther` component. The component does not require any props.
228
-
229
- By default, your account, when created, has two other-page placements named `other-nosto-1` and `other-nosto-2`. You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example.
230
-
231
- ```jsx
232
- import { NostoOther, NostoPlacement } from "@nosto/nosto-react"
233
- <div className="other-page">
234
- ... ... ...
235
- <NostoPlacement id="other-nosto-1" />
236
- <NostoPlacement id="other-nosto-2" />
237
- <NostoOther />
238
- </div>
239
- ```
240
-
241
- ##### Personalising your order confirmation page
242
-
243
- You can personalise your order-confirmation/thank-you page by using the `NostoOrder` component. The component requires that you provide it with the details of the order.
244
-
245
- By default, your account, when created, has one other-page placement named `thankyou-nosto-1`. You may omit this and use any identifier you need. The identifier used here is simply provided to illustrate the example.
246
-
247
- ```jsx
248
- import { NostoOrder, NostoPlacement } from "@nosto/nosto-react"
249
- <div className="thankyou-page">
250
- ... ... ...
251
- <NostoPlacement id="thankyou-nosto-1" />
252
- <NostoOrder order={order} />
253
- </div>
254
- ```
255
-
256
- ### Hook alternatives
257
-
258
- For all the page type specific components hooks are also provided with the same props
259
-
260
- - useNosto404
261
- - useNostoOther
262
- - useNostoCheckout
263
- - useNostoProduct
264
- - useNostoCategory
265
- - useNostoSearch
266
- - useNostOrder
267
- - useNostoHome
268
-
269
- ### Detailed technical documentation
270
-
271
- Find our latest technical specs and documentation hosted [here](https://nosto.github.io/nosto-react).
272
-
273
- ### Feedback
274
-
275
- If you've found a feature missing or you would like to report an issue, simply [open up an issue](https://github.com/nosto/nosto-react/issues/new) and let us know.
276
-
277
- We're always collecting feedback and learning from your use-cases. If you find your self customising widgets and forking the repo to make patches - do drop a message. We'd love to know more and understand how we can make React Nosto an even slicker library for you.
278
-
279
- ### Contributing
280
-
281
- Please take a moment to review the guidelines for contributing.
282
-
283
- ### License
284
-
285
- BSD 3-Clause License
30
+ [Library TypeDoc page](https://nosto.github.io/nosto-react/) includes detailed library helpers documentation and examples.
package/dist/index.d.ts CHANGED
@@ -9,8 +9,6 @@ import { ReactElement } from 'react';
9
9
  import { ReactNode } from 'react';
10
10
  import { RenderMode } from '@nosto/nosto-js/client';
11
11
 
12
- declare type AnyFunction = (...args: unknown[]) => unknown;
13
-
14
12
  export { Cart }
15
13
 
16
14
  export { Customer }
@@ -116,7 +114,7 @@ export declare interface NostoContextType {
116
114
  account: string;
117
115
  clientScriptLoaded: boolean;
118
116
  currentVariation?: string;
119
- renderFunction?: AnyFunction;
117
+ renderFunction?: (...args: unknown[]) => unknown;
120
118
  responseMode: RenderMode;
121
119
  recommendationComponent?: RecommendationComponent;
122
120
  }
@@ -296,7 +294,7 @@ export declare function NostoProvider(props: NostoProviderProps): JSX.Element;
296
294
  /**
297
295
  * @group Components
298
296
  */
299
- declare interface NostoProviderProps {
297
+ export declare interface NostoProviderProps {
300
298
  /**
301
299
  * Indicates merchant id
302
300
  */
@@ -412,10 +410,12 @@ export declare interface Recommendation {
412
410
  params: unknown;
413
411
  }
414
412
 
415
- declare type RecommendationComponent = ReactElement<{
413
+ export declare type RecommendationComponent = ReactElement<{
416
414
  nostoRecommendation: Recommendation;
417
415
  }>;
418
416
 
417
+ export { RenderMode }
418
+
419
419
  /**
420
420
  * @group Types
421
421
  */
@@ -430,9 +430,9 @@ export declare type ScriptLoadOptions = {
430
430
  attributes?: Record<string, string>;
431
431
  };
432
432
 
433
- declare type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}` ? `${T}${Capitalize<SnakeToCamelCase<U>>}` : S;
433
+ export declare type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}` ? `${T}${Capitalize<SnakeToCamelCase<U>>}` : S;
434
434
 
435
- declare type ToCamelCase<T> = T extends (infer U)[] ? ToCamelCase<U>[] : T extends Date ? T : T extends object ? {
435
+ export declare type ToCamelCase<T> = T extends (infer U)[] ? ToCamelCase<U>[] : T extends Date ? T : T extends object ? {
436
436
  [K in keyof T as SnakeToCamelCase<K & string>]: ToCamelCase<T[K]>;
437
437
  } : T;
438
438
 
package/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- (function(s,u){typeof exports=="object"&&typeof module<"u"?u(exports,require("react"),require("react/jsx-runtime"),require("react-dom/client")):typeof define=="function"&&define.amd?define(["exports","react","react/jsx-runtime","react-dom/client"],u):(s=typeof globalThis<"u"?globalThis:s||self,u(s["@nosto/nosto-react"]={},s.React,s["react/jsx-runtime"],s.client))})(this,function(s,u,y,V){"use strict";const S=u.createContext({account:"",currentVariation:"",responseMode:"HTML",clientScriptLoaded:!1});function N(){return u.useContext(S)}const g=e=>String(e)==="[object Object]";function j(e){if(!g(e))return!1;const t=e.constructor;if(t===void 0)return!0;const n=t.prototype;return!(!g(n)||!n.hasOwnProperty("isPrototypeOf"))}function P(e,t){if(e===t)return!0;if(e instanceof Date&&t instanceof Date)return e.getTime()===t.getTime();if(e instanceof Array&&t instanceof Array)return e.length!==t.length?!1:e.every((n,o)=>P(n,t[o]));if(j(e)&&j(t)){const n=Object.entries(e);return n.length!==Object.keys(t).length?!1:n.every(([o,r])=>P(r,t[o]))}return!1}function L(e,t){return u.useEffect(e,F(t))}function F(e){const t=u.useRef(e),n=u.useRef(0);return P(e,t.current)||(t.current=e,n.current+=1),u.useMemo(()=>t.current,[n.current])}function v(){window.nostojs=window.nostojs??function(e){(window.nostojs.q=window.nostojs.q??[]).push(e)}}async function p(e){return window.nostojs(e)}typeof window<"u"&&(v(),p(e=>{e.internal.getSettings()}));function z(){return typeof window.nosto<"u"}const U={production:"https://connect.nosto.com/",staging:"https://connect.staging.nosto.com/",local:"https://connect.nosto.com/"};function R(e){return U[e??"production"]}function $({merchantId:e,env:t,options:n,shopifyInternational:o,scriptLoader:r}){var c,a;const i=document.querySelector("script[nosto-language], script[nosto-market-id]"),d=String((o==null?void 0:o.marketId)||""),l=(o==null?void 0:o.language)||"",O=(i==null?void 0:i.getAttribute("nosto-language"))!==l||(i==null?void 0:i.getAttribute("nosto-market-id"))!==d;if(!i||O){const h=document.querySelector("#nosto-sandbox");(c=i==null?void 0:i.parentNode)==null||c.removeChild(i),(a=h==null?void 0:h.parentNode)==null||a.removeChild(h);const w=new URL("/script/shopify/market/nosto.js",R(t));w.searchParams.append("merchant",e),w.searchParams.append("market",d),w.searchParams.append("locale",l.toLowerCase());const ue={...n==null?void 0:n.attributes,"nosto-language":l,"nosto-market-id":d};return(r??E)(w.toString(),{...n,attributes:ue})}return Promise.resolve()}function G(e){if(e.shopifyInternational)return $(e);const{merchantId:t,env:n,options:o,scriptLoader:r}=e,c=r??E,a=new URL(`/include/${t}`,R(n));return c(a.toString(),o)}function E(e,t){return new Promise((n,o)=>{const r=document.createElement("script");r.src=e,r.async=!0,r.type="text/javascript",r.onload=()=>n(),r.onerror=()=>o(),Object.entries((t==null?void 0:t.attributes)??{}).forEach(([c,a])=>r.setAttribute(c,a)),(t==null?void 0:t.position)==="head"?document.head.appendChild(r):document.body.appendChild(r)})}typeof window<"u"&&v();function m(e,t,n){const{clientScriptLoaded:o}=N();(n!=null&&n.deep?L:u.useEffect)(()=>{o&&p(e)},[o,...t??[]])}function J(e){return u.cloneElement(e.recommendationComponent,{nostoRecommendation:e.nostoRecommendation})}function A(e){p(t=>t.placements.injectCampaigns(e))}function W(e){if(!window.nostojs)throw new Error("Nosto has not yet been initialized");A(e.recommendations)}function f(){const{responseMode:e,recommendationComponent:t}=N(),n=u.useRef({});if(e=="HTML")return{renderCampaigns:W};if(!t)throw new Error("recommendationComponent is required for client-side rendering using hook");function o(r){var a,i;A(((a=r.campaigns)==null?void 0:a.content)??{});const c=((i=r.campaigns)==null?void 0:i.recommendations)??{};for(const d in c){const l=c[d],O="#"+d,h=document.querySelector(O);h&&(n.current[d]||(n.current[d]=V.createRoot(h)),n.current[d].render(y.jsx(J,{recommendationComponent:t,nostoRecommendation:l})))}}return{renderCampaigns:o}}function b(e){const{renderCampaigns:t}=f();m(async n=>{const o=await n.defaultSession().viewNotFound().setPlacements((e==null?void 0:e.placements)||n.placements.getPlacements()).load();t(o)})}function Z(e){return b(e),null}function k({category:e,placements:t}){const{renderCampaigns:n}=f();m(async o=>{const r=await o.defaultSession().viewCategory(e).setPlacements(t||o.placements.getPlacements()).load();n(r)},[e])}function B(e){return k(e),null}function I(e){const{renderCampaigns:t}=f();m(async n=>{const o=await n.defaultSession().viewCart().setPlacements((e==null?void 0:e.placements)||n.placements.getPlacements()).load();t(o)})}function K(e){return I(e),null}function T(e){const{renderCampaigns:t}=f();m(async n=>{const o=await n.defaultSession().viewFrontPage().setPlacements((e==null?void 0:e.placements)||n.placements.getPlacements()).load();t(o)})}function Q(e){return T(e),null}function C(e){return!e||typeof e!="object"||X(e)||Y(e)?e:Array.isArray(e)?e.map(C):Object.keys(e).reduce((t,n)=>{const o=n[0].toLowerCase()+n.slice(1).replace(/([A-Z]+)/g,(r,c)=>"_"+c.toLowerCase());return t[o]=C(e[n]),t},{})}function X(e){return Object.prototype.toString.call(e)==="[object Date]"}function Y(e){return Object.prototype.toString.call(e)==="[object RegExp]"}function _({order:e,placements:t}){const{renderCampaigns:n}=f();m(async o=>{const r=await o.defaultSession().addOrder(C(e)).setPlacements(t||o.placements.getPlacements()).load();n(r)},[e],{deep:!0})}function x(e){return _(e),null}function q(e){const{renderCampaigns:t}=f();m(async n=>{const o=await n.defaultSession().viewOther().setPlacements((e==null?void 0:e.placements)||n.placements.getPlacements()).load();t(o)})}function ee(e){return q(e),null}function te({id:e,pageType:t,children:n}){return y.jsx("div",{className:"nosto_element",id:e,children:n},e+(t||""))}function M({product:e,tagging:t,placements:n,reference:o}){const{renderCampaigns:r}=f();if(t&&!t.product_id)throw new Error("The product object must contain a product_id property");const c=(t==null?void 0:t.product_id)??e;m(async a=>{const i=a.defaultSession().viewProduct(t??e).setPlacements(n||a.placements.getPlacements());o&&i.setRef(c,o);const d=await i.load();r(d)},[c,t==null?void 0:t.selected_sku_id])}function ne(e){return M(e),null}function oe(e,t){return new Promise((n,o)=>{const r=document.createElement("script");r.type="text/javascript",r.src=e,r.async=!0,r.onload=()=>n(),r.onerror=()=>o(),Object.entries((t==null?void 0:t.attributes)??{}).forEach(([c,a])=>r.setAttribute(c,a)),(t==null?void 0:t.position)==="head"?document.head.appendChild(r):document.body.appendChild(r)})}const re={"nosto-client-script":""};function se(e){const{scriptLoader:t=oe,account:n,shopifyMarkets:o,loadScript:r=!0}=e,[c,a]=u.useState(!1);return u.useEffect(()=>{function i(){a(!0)}if(v(),p(l=>l.setAutoLoad(!1)),!r){p(i);return}async function d(){await G({merchantId:n,shopifyInternational:o,options:{attributes:re},scriptLoader:t}),i()}(!z()||o)&&d()},[o==null?void 0:o.marketId,o==null?void 0:o.language]),{clientScriptLoaded:c}}function ce(e){const{account:t,multiCurrency:n=!1,children:o,recommendationComponent:r,renderMode:c}=e,a=n?e.currentVariation:"";if(r&&!u.isValidElement(r))throw new Error("The recommendationComponent prop must be a valid React element. Please provide a valid React element.");const i=c||(r?"JSON_ORIGINAL":"HTML"),{clientScriptLoaded:d}=se(e);return d&&p(l=>{l.defaultSession().setVariation(a).setResponseMode(i)}),y.jsx(S.Provider,{value:{account:t,clientScriptLoaded:d,currentVariation:a,responseMode:i,recommendationComponent:r},children:o})}function H({query:e,placements:t}){const{renderCampaigns:n}=f();m(async o=>{const r=await o.defaultSession().viewSearch(e).setPlacements(t||o.placements.getPlacements()).load();n(r)},[e])}function ie(e){return H(e),null}function D({cart:e,customer:t}={}){const{clientScriptLoaded:n}=N();L(()=>{const o=e?C(e):void 0,r=t?C(t):void 0;n&&p(c=>{c.defaultSession().setCart(o).setCustomer(r).viewOther().load({skipPageViews:!0})})},[n,e,t])}function ae(e){return D(e),null}s.Nosto404=Z,s.NostoCategory=B,s.NostoCheckout=K,s.NostoContext=S,s.NostoHome=Q,s.NostoOrder=x,s.NostoOther=ee,s.NostoPlacement=te,s.NostoProduct=ne,s.NostoProvider=ce,s.NostoSearch=ie,s.NostoSession=ae,s.useNosto404=b,s.useNostoCategory=k,s.useNostoCheckout=I,s.useNostoContext=N,s.useNostoHome=T,s.useNostoOrder=_,s.useNostoOther=q,s.useNostoProduct=M,s.useNostoSearch=H,s.useNostoSession=D,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})});
1
+ (function(s,u){typeof exports=="object"&&typeof module<"u"?u(exports,require("react"),require("react/jsx-runtime"),require("react-dom/client")):typeof define=="function"&&define.amd?define(["exports","react","react/jsx-runtime","react-dom/client"],u):(s=typeof globalThis<"u"?globalThis:s||self,u(s["@nosto/nosto-react"]={},s.React,s["react/jsx-runtime"],s.client))})(this,function(s,u,y,V){"use strict";const S=u.createContext({account:"",currentVariation:"",responseMode:"HTML",clientScriptLoaded:!1});function N(){return u.useContext(S)}const g=e=>String(e)==="[object Object]";function j(e){if(!g(e))return!1;const t=e.constructor;if(t===void 0)return!0;const n=t.prototype;return!(!g(n)||!n.hasOwnProperty("isPrototypeOf"))}function P(e,t){if(e===t)return!0;if(e instanceof Date&&t instanceof Date)return e.getTime()===t.getTime();if(e instanceof Array&&t instanceof Array)return e.length!==t.length?!1:e.every((n,o)=>P(n,t[o]));if(j(e)&&j(t)){const n=Object.entries(e);return n.length!==Object.keys(t).length?!1:n.every(([o,r])=>P(r,t[o]))}return!1}function L(e,t){return u.useEffect(e,F(t))}function F(e){const t=u.useRef(e),n=u.useRef(0);return P(e,t.current)||(t.current=e,n.current+=1),u.useMemo(()=>t.current,[n.current])}function v(){window.nostojs=window.nostojs??function(e){(window.nostojs.q=window.nostojs.q??[]).push(e)}}async function p(e){return window.nostojs(e)}typeof window<"u"&&(v(),p(e=>{e.internal.getSettings()}));function z(){return typeof window.nosto<"u"}const U={production:"https://connect.nosto.com/",staging:"https://connect.staging.nosto.com/",local:"https://connect.nosto.com/"};function R(e){return U[e??"production"]}function $({merchantId:e,env:t,options:n,shopifyInternational:o,scriptLoader:r}){var c,a;const i=document.querySelector("script[nosto-language], script[nosto-market-id]"),d=String((o==null?void 0:o.marketId)||""),l=(o==null?void 0:o.language)||"",O=(i==null?void 0:i.getAttribute("nosto-language"))!==l||(i==null?void 0:i.getAttribute("nosto-market-id"))!==d;if(!i||O){const h=document.querySelector("#nosto-sandbox");(c=i==null?void 0:i.parentNode)==null||c.removeChild(i),(a=h==null?void 0:h.parentNode)==null||a.removeChild(h);const w=new URL("/script/shopify/market/nosto.js",R(t));w.searchParams.append("merchant",e),w.searchParams.append("market",d),w.searchParams.append("locale",l.toLowerCase());const ue={...n==null?void 0:n.attributes,"nosto-language":l,"nosto-market-id":d};return(r??E)(w.toString(),{...n,attributes:ue})}return Promise.resolve()}function G(e){if(e.shopifyInternational)return $(e);const{merchantId:t,env:n,options:o,scriptLoader:r}=e,c=r??E,a=new URL(`/include/${t}`,R(n));return c(a.toString(),o)}function E(e,t){return new Promise((n,o)=>{const r=document.createElement("script");r.src=e,r.async=!0,r.type="text/javascript",r.onload=()=>n(),r.onerror=()=>o(),Object.entries((t==null?void 0:t.attributes)??{}).forEach(([c,a])=>r.setAttribute(c,a)),(t==null?void 0:t.position)==="head"?document.head.appendChild(r):document.body.appendChild(r)})}typeof window<"u"&&v();function m(e,t,n){const{clientScriptLoaded:o}=N();(n!=null&&n.deep?L:u.useEffect)(()=>{o&&p(e)},[o,...t??[]])}function J(e){return u.cloneElement(e.recommendationComponent,{nostoRecommendation:e.nostoRecommendation})}function k(e){p(t=>t.placements.injectCampaigns(e))}function W(e){if(!window.nostojs)throw new Error("Nosto has not yet been initialized");k(e.recommendations)}function f(){const{responseMode:e,recommendationComponent:t}=N(),n=u.useRef({});if(e=="HTML")return{renderCampaigns:W};if(!t)throw new Error("recommendationComponent is required for client-side rendering using hook");function o(r){var a,i;k(((a=r.campaigns)==null?void 0:a.content)??{});const c=((i=r.campaigns)==null?void 0:i.recommendations)??{};for(const d in c){const l=c[d],O="#"+d,h=document.querySelector(O);h&&(n.current[d]||(n.current[d]=V.createRoot(h)),n.current[d].render(y.jsx(J,{recommendationComponent:t,nostoRecommendation:l})))}}return{renderCampaigns:o}}function A(e){const{renderCampaigns:t}=f();m(async n=>{const o=await n.defaultSession().viewNotFound().setPlacements((e==null?void 0:e.placements)||n.placements.getPlacements()).load();t(o)})}function Z(e){return A(e),null}function b({category:e,placements:t}){const{renderCampaigns:n}=f();m(async o=>{const r=await o.defaultSession().viewCategory(e).setPlacements(t||o.placements.getPlacements()).load();n(r)},[e])}function B(e){return b(e),null}function I(e){const{renderCampaigns:t}=f();m(async n=>{const o=await n.defaultSession().viewCart().setPlacements((e==null?void 0:e.placements)||n.placements.getPlacements()).load();t(o)})}function K(e){return I(e),null}function T(e){const{renderCampaigns:t}=f();m(async n=>{const o=await n.defaultSession().viewFrontPage().setPlacements((e==null?void 0:e.placements)||n.placements.getPlacements()).load();t(o)})}function Q(e){return T(e),null}function C(e){return!e||typeof e!="object"||X(e)||Y(e)?e:Array.isArray(e)?e.map(C):Object.keys(e).reduce((t,n)=>{const o=n[0].toLowerCase()+n.slice(1).replace(/([A-Z]+)/g,(r,c)=>"_"+c.toLowerCase());return t[o]=C(e[n]),t},{})}function X(e){return Object.prototype.toString.call(e)==="[object Date]"}function Y(e){return Object.prototype.toString.call(e)==="[object RegExp]"}function _({order:e,placements:t}){const{renderCampaigns:n}=f();m(async o=>{const r=await o.defaultSession().addOrder(C(e)).setPlacements(t||o.placements.getPlacements()).load();n(r)},[e],{deep:!0})}function x(e){return _(e),null}function q(e){const{renderCampaigns:t}=f();m(async n=>{const o=await n.defaultSession().viewOther().setPlacements((e==null?void 0:e.placements)||n.placements.getPlacements()).load();t(o)})}function ee(e){return q(e),null}function te({id:e,pageType:t,children:n}){return y.jsx("div",{className:"nosto_element",id:e,children:n},e+(t||""))}function M({product:e,tagging:t,placements:n,reference:o}){const{renderCampaigns:r}=f();if(t&&!t.product_id)throw new Error("The product object must contain a product_id property");const c=(t==null?void 0:t.product_id)??e;m(async a=>{const i=a.defaultSession().viewProduct(t??e).setPlacements(n||a.placements.getPlacements());o&&i.setRef(c,o);const d=await i.load();r(d)},[c,t==null?void 0:t.selected_sku_id])}function ne(e){return M(e),null}function oe(e,t){return new Promise((n,o)=>{const r=document.createElement("script");r.type="text/javascript",r.src=e,r.async=!0,r.onload=()=>n(),r.onerror=()=>o(),Object.entries((t==null?void 0:t.attributes)??{}).forEach(([c,a])=>r.setAttribute(c,a)),(t==null?void 0:t.position)==="head"?document.head.appendChild(r):document.body.appendChild(r)})}const re={"nosto-client-script":""};function se(e){const{scriptLoader:t=oe,account:n,shopifyMarkets:o,loadScript:r=!0}=e,[c,a]=u.useState(!1);return u.useEffect(()=>{function i(){a(!0)}if(v(),p(l=>l.setAutoLoad(!1)),!r){p(i);return}async function d(){await G({merchantId:n,shopifyInternational:o,options:{attributes:re},scriptLoader:t}),i()}(!z()||o)&&d()},[o==null?void 0:o.marketId,o==null?void 0:o.language]),{clientScriptLoaded:c}}function ce(e){const{account:t,multiCurrency:n=!1,children:o,recommendationComponent:r,renderMode:c}=e,a=n?e.currentVariation:"";if(r&&!u.isValidElement(r))throw new Error("The recommendationComponent prop must be a valid React element. Please provide a valid React element.");const i=c||(r?"JSON_ORIGINAL":"HTML"),{clientScriptLoaded:d}=se(e);return d&&p(l=>{l.defaultSession().setVariation(a).setResponseMode(i)}),y.jsx(S.Provider,{value:{account:t,clientScriptLoaded:d,currentVariation:a,responseMode:i,recommendationComponent:r},children:o})}function H({query:e,placements:t}){const{renderCampaigns:n}=f();m(async o=>{const r=await o.defaultSession().viewSearch(e).setPlacements(t||o.placements.getPlacements()).load();n(r)},[e])}function ie(e){return H(e),null}function D({cart:e,customer:t}={}){const{clientScriptLoaded:n}=N();L(()=>{const o=e?C(e):void 0,r=t?C(t):void 0;n&&p(c=>{c.defaultSession().setCart(o).setCustomer(r).viewOther().load({skipPageViews:!0})})},[n,e,t])}function ae(e){return D(e),null}s.Nosto404=Z,s.NostoCategory=B,s.NostoCheckout=K,s.NostoContext=S,s.NostoHome=Q,s.NostoOrder=x,s.NostoOther=ee,s.NostoPlacement=te,s.NostoProduct=ne,s.NostoProvider=ce,s.NostoSearch=ie,s.NostoSession=ae,s.useNosto404=A,s.useNostoCategory=b,s.useNostoCheckout=I,s.useNostoContext=N,s.useNostoHome=T,s.useNostoOrder=_,s.useNostoOther=q,s.useNostoProduct=M,s.useNostoSearch=H,s.useNostoSession=D,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nosto/nosto-react",
3
3
  "description": "Component library to simply implementing Nosto on React.",
4
- "version": "2.7.0",
4
+ "version": "2.7.1",
5
5
  "author": "Mridang Agarwalla, Dominik Gilg",
6
6
  "license": "ISC",
7
7
  "repository": {
@@ -24,7 +24,7 @@
24
24
  "prettier:fix": "prettier '{src,spec}/**' --write",
25
25
  "test": "vitest run --silent",
26
26
  "test-loud": "vitest run",
27
- "typedoc": "typedoc src/index.ts"
27
+ "typedoc": "typedoc --treatWarningsAsErrors src/index.ts"
28
28
  },
29
29
  "files": [
30
30
  "dist",
@@ -54,7 +54,7 @@
54
54
  "react-router-dom": "^7.0.1",
55
55
  "rimraf": "^6.0.1",
56
56
  "rollup-plugin-visualizer": "^5.13.1",
57
- "typedoc": "^0.27.2",
57
+ "typedoc": "^0.28.0",
58
58
  "typescript": "^5.6.3",
59
59
  "typescript-eslint": "^8.13.0",
60
60
  "vite": "^6.0.3",
package/src/context.ts CHANGED
@@ -2,8 +2,6 @@ import { createContext, ReactElement } from "react"
2
2
  import { Recommendation } from "./types"
3
3
  import { RenderMode } from "@nosto/nosto-js/client"
4
4
 
5
- type AnyFunction = (...args: unknown[]) => unknown
6
-
7
5
  export type RecommendationComponent = ReactElement<{
8
6
  nostoRecommendation: Recommendation
9
7
  }>
@@ -15,7 +13,7 @@ export interface NostoContextType {
15
13
  account: string
16
14
  clientScriptLoaded: boolean
17
15
  currentVariation?: string
18
- renderFunction?: AnyFunction
16
+ renderFunction?: (...args: unknown[]) => unknown
19
17
  responseMode: RenderMode
20
18
  recommendationComponent?: RecommendationComponent
21
19
  }
package/src/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  /* eslint-disable barrel-files/avoid-barrel-files */
2
- export type { Product, PushedCustomer as Customer, Cart, WebsiteOrder as Order } from "@nosto/nosto-js/client"
2
+ export type { Product, PushedCustomer as Customer, Cart, WebsiteOrder as Order, RenderMode } from "@nosto/nosto-js/client"
3
3
  export type { Recommendation } from "./types"
4
+ export type { SnakeToCamelCase, ToCamelCase } from "./utils/types"
4
5
  export { type ScriptLoadOptions } from "./hooks/scriptLoader"
5
- export { NostoContext, type NostoContextType } from "./context"
6
+ export { NostoContext, type NostoContextType, type RecommendationComponent } from "./context"
6
7
  export { useNostoContext } from "./hooks/useNostoContext"
7
8
  export { Nosto404 } from "./components/Nosto404"
8
9
  export { NostoCategory } from "./components/NostoCategory"
@@ -12,7 +13,7 @@ export { NostoOrder } from "./components/NostoOrder"
12
13
  export { NostoOther } from "./components/NostoOther"
13
14
  export { NostoPlacement, type NostoPlacementProps } from "./components/NostoPlacement"
14
15
  export { NostoProduct } from "./components/NostoProduct"
15
- export { NostoProvider } from "./components/NostoProvider"
16
+ export { NostoProvider, type NostoProviderProps } from "./components/NostoProvider"
16
17
  export { NostoSearch } from "./components/NostoSearch"
17
18
  export { NostoSession } from "./components/NostoSession"
18
19
  export { useNosto404, type Nosto404Props } from "./hooks/useNosto404"
@@ -1,4 +1,4 @@
1
- type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}`
1
+ export type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}`
2
2
  ? `${T}${Capitalize<SnakeToCamelCase<U>>}`
3
3
  : S
4
4
 
@@ -13,7 +13,7 @@ export type ToCamelCase<T> = T extends (infer U)[]
13
13
  }
14
14
  : T
15
15
 
16
- type CamelToSnakeCase<S extends string> = S extends `${infer T}${infer U}`
16
+ export type CamelToSnakeCase<S extends string> = S extends `${infer T}${infer U}`
17
17
  ? U extends Uncapitalize<U>
18
18
  ? `${Lowercase<T>}${CamelToSnakeCase<U>}`
19
19
  : `${Lowercase<T>}_${CamelToSnakeCase<Uncapitalize<U>>}`