@dutchiesdk/ecommerce-extensions-sdk 0.36.0 → 0.36.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.
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ useIsHydrated: ()=>useIsHydrated,
28
+ ClientOnly: ()=>ClientOnly
29
+ });
30
+ const jsx_runtime_namespaceObject = require("react/jsx-runtime");
31
+ const external_react_namespaceObject = require("react");
32
+ function useIsHydrated() {
33
+ const [isHydrated, setIsHydrated] = (0, external_react_namespaceObject.useState)(false);
34
+ (0, external_react_namespaceObject.useEffect)(()=>{
35
+ setIsHydrated(true);
36
+ }, []);
37
+ return isHydrated;
38
+ }
39
+ function ClientOnly({ children, fallback = null }) {
40
+ const isHydrated = useIsHydrated();
41
+ if (!isHydrated) return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(jsx_runtime_namespaceObject.Fragment, {
42
+ children: fallback
43
+ });
44
+ return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(jsx_runtime_namespaceObject.Fragment, {
45
+ children: children
46
+ });
47
+ }
48
+ exports.ClientOnly = __webpack_exports__.ClientOnly;
49
+ exports.useIsHydrated = __webpack_exports__.useIsHydrated;
50
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
51
+ "ClientOnly",
52
+ "useIsHydrated"
53
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
54
+ Object.defineProperty(exports, '__esModule', {
55
+ value: true
56
+ });
@@ -0,0 +1,25 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * True once the component has mounted on the client. Always `false` during server
4
+ * rendering and during the client's first render (pre-hydration) -- this is what
5
+ * lets consumers avoid a hydration mismatch: server and first client render agree.
6
+ */
7
+ export declare function useIsHydrated(): boolean;
8
+ export type ClientOnlyProps = {
9
+ children: ReactNode;
10
+ /** Rendered on the server and during the pre-hydration client render. Defaults to nothing. */
11
+ fallback?: ReactNode;
12
+ };
13
+ /**
14
+ * Renders `children` only once mounted on the client; renders `fallback` (default: nothing)
15
+ * on the server and during the initial client render.
16
+ *
17
+ * Use this for anything that is genuinely browser-only -- maps, video players,
18
+ * geolocation-driven widgets, anything reading `window`/`document` at render time --
19
+ * instead of a hand-rolled `typeof window !== 'undefined'` guard. A guard like that
20
+ * still causes a hydration mismatch (the server renders one thing, the client's first
21
+ * render renders another); `ClientOnly` renders the *same* fallback on the server and
22
+ * the first client pass, then swaps to the real content post-hydration, so there's
23
+ * nothing for React to reconcile against.
24
+ */
25
+ export declare function ClientOnly({ children, fallback }: ClientOnlyProps): import("react/jsx-runtime").JSX.Element;
@@ -25,18 +25,24 @@ var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  DataBridgeContext: ()=>DataBridgeContext,
28
+ useCommerceData: ()=>useCommerceData,
28
29
  DataBridgeVersion: ()=>DataBridgeVersion,
29
30
  useAsyncLoader: ()=>useAsyncLoader,
30
31
  useDataBridge: ()=>useDataBridge
31
32
  });
32
33
  const external_react_namespaceObject = require("react");
33
- const DataBridgeVersion = '0.36.0';
34
+ const DataBridgeVersion = '0.36.1';
34
35
  const DataBridgeContext = (0, external_react_namespaceObject.createContext)(void 0);
35
36
  const useDataBridge = ()=>{
36
37
  const context = (0, external_react_namespaceObject.useContext)(DataBridgeContext);
37
38
  if (void 0 === context) throw new Error('useDataBridge must be used within a DataBridgeProvider');
38
39
  return context;
39
40
  };
41
+ const EMPTY_COMMERCE_DATA = {};
42
+ const useCommerceData = ()=>{
43
+ const { data } = useDataBridge();
44
+ return data ?? EMPTY_COMMERCE_DATA;
45
+ };
40
46
  const useAsyncLoader = (fn, params)=>{
41
47
  const [data, setData] = (0, external_react_namespaceObject.useState)(null);
42
48
  (0, external_react_namespaceObject.useEffect)(()=>{
@@ -53,11 +59,13 @@ const useAsyncLoader = (fn, params)=>{
53
59
  exports.DataBridgeContext = __webpack_exports__.DataBridgeContext;
54
60
  exports.DataBridgeVersion = __webpack_exports__.DataBridgeVersion;
55
61
  exports.useAsyncLoader = __webpack_exports__.useAsyncLoader;
62
+ exports.useCommerceData = __webpack_exports__.useCommerceData;
56
63
  exports.useDataBridge = __webpack_exports__.useDataBridge;
57
64
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
58
65
  "DataBridgeContext",
59
66
  "DataBridgeVersion",
60
67
  "useAsyncLoader",
68
+ "useCommerceData",
61
69
  "useDataBridge"
62
70
  ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
63
71
  Object.defineProperty(exports, '__esModule', {
@@ -19,9 +19,29 @@ export declare const DataBridgeContext: import("react").Context<CommerceComponen
19
19
  * @returns The data bridge context.
20
20
  */
21
21
  export declare const useDataBridge: () => CommerceComponentsDataInterface;
22
+ /**
23
+ * Reads the V2 synchronous commerce data (see `CommerceDataV2`) off the data bridge.
24
+ * Prefer this over `useDataBridge().dataLoaders` in new theme code -- no async, no
25
+ * `useAsyncLoader`, no loading state to manage for data the host already has. Fields
26
+ * are `undefined` until the host resolves them for the current page; this returns an
27
+ * empty object (not `undefined`) when the host hasn't populated `data` at all, so
28
+ * `useCommerceData().brands` is always safe to read without an extra null check.
29
+ */
30
+ export declare const useCommerceData: () => NonNullable<CommerceComponentsDataInterface["data"]>;
22
31
  /**
23
32
  * A hook to load data asynchronously.
24
33
  *
34
+ * CSR-only by design: `useEffect` never runs during server rendering, so this can only
35
+ * ever produce a loading placeholder on the server. We looked at making this
36
+ * SSR-available (a keyed, host-resolvable variant with a collect-then-render pass) and
37
+ * decided against shipping it for now -- the host doesn't yet prefetch most theme data
38
+ * server-side (see PLAN.md's "V2 pivot"/SSR-today correction), so a general SSR
39
+ * mechanism here would have zero real consumers. Host-provided commerce data should go
40
+ * through `useCommerceData()` (`CommerceDataV2`) instead, which the host resolves
41
+ * synchronously as its own SSR data-fetching expands. This hook remains the right tool
42
+ * for genuinely dynamic, per-call data (e.g. `dataLoaders.integrationValue(key)`) and
43
+ * theme-original async work that the host has no way to pre-resolve.
44
+ *
25
45
  * @param fn - The function to load the data.
26
46
  * @param params - Optional parameters to pass to the function.
27
47
  * @returns The data and a boolean indicating if the data is loading.
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ useHostLocation: ()=>useHostLocation
28
+ });
29
+ const external_ecommerce_data_bridge_cjs_namespaceObject = require("./ecommerce-data-bridge.cjs");
30
+ const EMPTY_LOCATION = {
31
+ path: '',
32
+ search: ''
33
+ };
34
+ function useHostLocation() {
35
+ const { request } = (0, external_ecommerce_data_bridge_cjs_namespaceObject.useDataBridge)();
36
+ return request ?? EMPTY_LOCATION;
37
+ }
38
+ exports.useHostLocation = __webpack_exports__.useHostLocation;
39
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
40
+ "useHostLocation"
41
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
42
+ Object.defineProperty(exports, '__esModule', {
43
+ value: true
44
+ });
@@ -0,0 +1,14 @@
1
+ import type { HostLocation } from '../types/interface';
2
+ /**
3
+ * SSR-safe access to the current request's path/query, sourced from the host via the
4
+ * data bridge instead of `window.location`. Populated identically on the server and the
5
+ * client, so unlike `typeof window !== 'undefined' ? window.location.pathname : ''`, this
6
+ * never produces a hydration mismatch (the guarded form renders `''` on the server and
7
+ * the client's first render, then the "real" value only once effects run -- two different
8
+ * outputs for what should be the same render).
9
+ *
10
+ * Returns an empty location (not undefined) when the host hasn't supplied one (e.g. a
11
+ * host that hasn't been upgraded to populate `request` yet), so call sites don't need
12
+ * their own fallback handling.
13
+ */
14
+ export declare function useHostLocation(): HostLocation;
@@ -0,0 +1,25 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * True once the component has mounted on the client. Always `false` during server
4
+ * rendering and during the client's first render (pre-hydration) -- this is what
5
+ * lets consumers avoid a hydration mismatch: server and first client render agree.
6
+ */
7
+ export declare function useIsHydrated(): boolean;
8
+ export type ClientOnlyProps = {
9
+ children: ReactNode;
10
+ /** Rendered on the server and during the pre-hydration client render. Defaults to nothing. */
11
+ fallback?: ReactNode;
12
+ };
13
+ /**
14
+ * Renders `children` only once mounted on the client; renders `fallback` (default: nothing)
15
+ * on the server and during the initial client render.
16
+ *
17
+ * Use this for anything that is genuinely browser-only -- maps, video players,
18
+ * geolocation-driven widgets, anything reading `window`/`document` at render time --
19
+ * instead of a hand-rolled `typeof window !== 'undefined'` guard. A guard like that
20
+ * still causes a hydration mismatch (the server renders one thing, the client's first
21
+ * render renders another); `ClientOnly` renders the *same* fallback on the server and
22
+ * the first client pass, then swaps to the real content post-hydration, so there's
23
+ * nothing for React to reconcile against.
24
+ */
25
+ export declare function ClientOnly({ children, fallback }: ClientOnlyProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,19 @@
1
+ import { Fragment, jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ function useIsHydrated() {
4
+ const [isHydrated, setIsHydrated] = useState(false);
5
+ useEffect(()=>{
6
+ setIsHydrated(true);
7
+ }, []);
8
+ return isHydrated;
9
+ }
10
+ function ClientOnly({ children, fallback = null }) {
11
+ const isHydrated = useIsHydrated();
12
+ if (!isHydrated) return /*#__PURE__*/ jsx(Fragment, {
13
+ children: fallback
14
+ });
15
+ return /*#__PURE__*/ jsx(Fragment, {
16
+ children: children
17
+ });
18
+ }
19
+ export { ClientOnly, useIsHydrated };
@@ -19,9 +19,29 @@ export declare const DataBridgeContext: import("react").Context<CommerceComponen
19
19
  * @returns The data bridge context.
20
20
  */
21
21
  export declare const useDataBridge: () => CommerceComponentsDataInterface;
22
+ /**
23
+ * Reads the V2 synchronous commerce data (see `CommerceDataV2`) off the data bridge.
24
+ * Prefer this over `useDataBridge().dataLoaders` in new theme code -- no async, no
25
+ * `useAsyncLoader`, no loading state to manage for data the host already has. Fields
26
+ * are `undefined` until the host resolves them for the current page; this returns an
27
+ * empty object (not `undefined`) when the host hasn't populated `data` at all, so
28
+ * `useCommerceData().brands` is always safe to read without an extra null check.
29
+ */
30
+ export declare const useCommerceData: () => NonNullable<CommerceComponentsDataInterface["data"]>;
22
31
  /**
23
32
  * A hook to load data asynchronously.
24
33
  *
34
+ * CSR-only by design: `useEffect` never runs during server rendering, so this can only
35
+ * ever produce a loading placeholder on the server. We looked at making this
36
+ * SSR-available (a keyed, host-resolvable variant with a collect-then-render pass) and
37
+ * decided against shipping it for now -- the host doesn't yet prefetch most theme data
38
+ * server-side (see PLAN.md's "V2 pivot"/SSR-today correction), so a general SSR
39
+ * mechanism here would have zero real consumers. Host-provided commerce data should go
40
+ * through `useCommerceData()` (`CommerceDataV2`) instead, which the host resolves
41
+ * synchronously as its own SSR data-fetching expands. This hook remains the right tool
42
+ * for genuinely dynamic, per-call data (e.g. `dataLoaders.integrationValue(key)`) and
43
+ * theme-original async work that the host has no way to pre-resolve.
44
+ *
25
45
  * @param fn - The function to load the data.
26
46
  * @param params - Optional parameters to pass to the function.
27
47
  * @returns The data and a boolean indicating if the data is loading.
@@ -1,11 +1,16 @@
1
1
  import { createContext, useContext, useEffect, useState } from "react";
2
- const DataBridgeVersion = '0.36.0';
2
+ const DataBridgeVersion = '0.36.1';
3
3
  const DataBridgeContext = createContext(void 0);
4
4
  const useDataBridge = ()=>{
5
5
  const context = useContext(DataBridgeContext);
6
6
  if (void 0 === context) throw new Error('useDataBridge must be used within a DataBridgeProvider');
7
7
  return context;
8
8
  };
9
+ const EMPTY_COMMERCE_DATA = {};
10
+ const useCommerceData = ()=>{
11
+ const { data } = useDataBridge();
12
+ return data ?? EMPTY_COMMERCE_DATA;
13
+ };
9
14
  const useAsyncLoader = (fn, params)=>{
10
15
  const [data, setData] = useState(null);
11
16
  useEffect(()=>{
@@ -19,4 +24,4 @@ const useAsyncLoader = (fn, params)=>{
19
24
  isLoading: null === data
20
25
  };
21
26
  };
22
- export { DataBridgeContext, DataBridgeVersion, useAsyncLoader, useDataBridge };
27
+ export { DataBridgeContext, DataBridgeVersion, useAsyncLoader, useCommerceData, useDataBridge };
@@ -0,0 +1,14 @@
1
+ import type { HostLocation } from '../types/interface';
2
+ /**
3
+ * SSR-safe access to the current request's path/query, sourced from the host via the
4
+ * data bridge instead of `window.location`. Populated identically on the server and the
5
+ * client, so unlike `typeof window !== 'undefined' ? window.location.pathname : ''`, this
6
+ * never produces a hydration mismatch (the guarded form renders `''` on the server and
7
+ * the client's first render, then the "real" value only once effects run -- two different
8
+ * outputs for what should be the same render).
9
+ *
10
+ * Returns an empty location (not undefined) when the host hasn't supplied one (e.g. a
11
+ * host that hasn't been upgraded to populate `request` yet), so call sites don't need
12
+ * their own fallback handling.
13
+ */
14
+ export declare function useHostLocation(): HostLocation;
@@ -0,0 +1,10 @@
1
+ import { useDataBridge } from "./ecommerce-data-bridge.js";
2
+ const EMPTY_LOCATION = {
3
+ path: '',
4
+ search: ''
5
+ };
6
+ function useHostLocation() {
7
+ const { request } = useDataBridge();
8
+ return request ?? EMPTY_LOCATION;
9
+ }
10
+ export { useHostLocation };
@@ -1,5 +1,7 @@
1
+ export * from './components/client-only';
1
2
  export * from './components/remote-boundary';
2
3
  export * from './context/ecommerce-data-bridge';
4
+ export * from './context/use-host-location';
3
5
  export * from './types/actions';
4
6
  export * from './types/data';
5
7
  export * from './types/ecommerce-extension';
@@ -7,3 +9,6 @@ export * from './types/events';
7
9
  export * from './types/filter-side-panel';
8
10
  export * from './types/interface';
9
11
  export * from './types/product-card';
12
+ export * from './utils/environment';
13
+ export * from './utils/safe-storage';
14
+ export * from './utils/use-isomorphic-layout-effect';
package/dist/esm/index.js CHANGED
@@ -1,5 +1,7 @@
1
+ export * from "./components/client-only.js";
1
2
  export * from "./components/remote-boundary.js";
2
3
  export * from "./context/ecommerce-data-bridge.js";
4
+ export * from "./context/use-host-location.js";
3
5
  export * from "./types/actions.js";
4
6
  export * from "./types/data.js";
5
7
  export * from "./types/ecommerce-extension.js";
@@ -7,3 +9,6 @@ export * from "./types/events.js";
7
9
  export * from "./types/filter-side-panel.js";
8
10
  export * from "./types/interface.js";
9
11
  export * from "./types/product-card.js";
12
+ export * from "./utils/environment.js";
13
+ export * from "./utils/safe-storage.js";
14
+ export * from "./utils/use-isomorphic-layout-effect.js";
@@ -1,10 +1,69 @@
1
1
  import type { Actions } from './actions';
2
- import type { Cart, DataLoaders, Dispensary, MenuContext, User } from './data';
2
+ import type { Brand, Cart, Category, Collection, DataLoaders, Dispensary, LoyaltyData, MenuContext, Product, Special, User } from './data';
3
+ /**
4
+ * The current request's URL info, provided by the host so theme code never needs
5
+ * `window.location` (which is unavailable during server rendering and, even when guarded
6
+ * with a `typeof window` check, causes a hydration mismatch since the server and the
7
+ * client's first render would otherwise disagree). Populated identically on the server
8
+ * and the client -- see `useHostLocation()`.
9
+ */
10
+ export type HostLocation = {
11
+ /** Pathname only, e.g. `/product/some-product` (no query string). */
12
+ path: string;
13
+ /** Raw query string, including the leading `?` if present, or `''`. */
14
+ search: string;
15
+ /** Request host/domain, when known. May be omitted in contexts where the host can't supply it. */
16
+ host?: string;
17
+ };
18
+ /**
19
+ * V2 commerce data: plain, already-resolved values instead of promise-returning loader
20
+ * functions (see {@link DataLoaders} / `dataLoaders`).
21
+ *
22
+ * Most `dataLoaders` fields wrap data the host has already fetched for its own
23
+ * (non-themed) rendering of the same page -- `dataLoaders.brands()` and
24
+ * `dataLoaders.categories()`, for example, are `Promise.resolve()`-wrapped values that
25
+ * are never actually asynchronous by the time a theme calls them. Forcing every theme
26
+ * to treat that data as async (via `useAsyncLoader` or hand-rolled promise handling) is
27
+ * unnecessary ceremony, and it's specifically what stood in the way of real SSR: an
28
+ * `useEffect`-based fetch can't produce data during server rendering, but a value that's
29
+ * already sitting in memory doesn't need an effect at all.
30
+ *
31
+ * Prefer this over `dataLoaders` in new theme code. Fields are `undefined` until the
32
+ * host has resolved them for the current page -- check for presence rather than
33
+ * awaiting. See the SSR readiness guide for the full rationale and the `dataLoaders`
34
+ * migration path.
35
+ */
36
+ export type CommerceDataV2 = {
37
+ brands?: Brand[];
38
+ categories?: Category[];
39
+ collections?: Collection[];
40
+ /** Only populated on a PDP. */
41
+ product?: Product | null;
42
+ products?: Product[];
43
+ specials?: Special[];
44
+ /** All dispensary locations of the chain. */
45
+ locations?: Dispensary[];
46
+ loyalty?: LoyaltyData | null;
47
+ };
3
48
  export type CommerceComponentsDataInterface = {
4
49
  actions: Actions;
5
50
  cart?: Cart;
51
+ /**
52
+ * V2 synchronous commerce data. Prefer this over `dataLoaders` in new theme code --
53
+ * see {@link CommerceDataV2}.
54
+ */
55
+ data?: CommerceDataV2;
56
+ /**
57
+ * @deprecated Prefer `data` (see {@link CommerceDataV2}) where the field you need is
58
+ * available there. Kept fully functional for backward compatibility -- not being
59
+ * removed, just superseded for new theme code. `integrationValue` has no `data`
60
+ * equivalent (it's a genuinely dynamic, caller-parameterized lookup) and stays here.
61
+ */
6
62
  dataLoaders: DataLoaders;
63
+ /** Note: this is the current dispensary location, not URL info -- see `request` for that. */
7
64
  location?: Dispensary;
8
65
  menuContext: MenuContext;
66
+ /** Current request URL info (path/search/host). See {@link HostLocation}. */
67
+ request?: HostLocation;
9
68
  user?: User;
10
69
  };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Canonical environment check. Use this instead of ad-hoc `typeof window !== 'undefined'`
3
+ * checks scattered through theme code -- a single source of truth is easier to audit
4
+ * (and to lint for) than dozens of hand-rolled variants.
5
+ */
6
+ export declare const isBrowser: () => boolean;
7
+ /** Inverse of {@link isBrowser}, provided so call sites can read either direction naturally. */
8
+ export declare const isServer: () => boolean;
@@ -0,0 +1,3 @@
1
+ const isBrowser = ()=>'undefined' != typeof window && 'undefined' != typeof document;
2
+ const isServer = ()=>!isBrowser();
3
+ export { isBrowser, isServer };
@@ -0,0 +1,14 @@
1
+ export type SafeStorage = {
2
+ getItem: (key: string) => string | null;
3
+ setItem: (key: string, value: string) => void;
4
+ removeItem: (key: string) => void;
5
+ clear: () => void;
6
+ };
7
+ /**
8
+ * `window.localStorage`, but safe to call unconditionally during server rendering
9
+ * (returns/no-ops instead of throwing `window is not defined`) and safe in
10
+ * environments where storage access itself can throw (private browsing, quota, policy).
11
+ */
12
+ export declare const safeLocalStorage: SafeStorage;
13
+ /** `window.sessionStorage` counterpart to {@link safeLocalStorage}. */
14
+ export declare const safeSessionStorage: SafeStorage;
@@ -0,0 +1,40 @@
1
+ import { isBrowser } from "./environment.js";
2
+ const noopStorage = {
3
+ getItem: ()=>null,
4
+ setItem: ()=>{},
5
+ removeItem: ()=>{},
6
+ clear: ()=>{}
7
+ };
8
+ function createSafeStorage(getStorage) {
9
+ return {
10
+ getItem: (key)=>{
11
+ if (!isBrowser()) return null;
12
+ try {
13
+ return getStorage().getItem(key);
14
+ } catch {
15
+ return null;
16
+ }
17
+ },
18
+ setItem: (key, value)=>{
19
+ if (!isBrowser()) return;
20
+ try {
21
+ getStorage().setItem(key, value);
22
+ } catch {}
23
+ },
24
+ removeItem: (key)=>{
25
+ if (!isBrowser()) return;
26
+ try {
27
+ getStorage().removeItem(key);
28
+ } catch {}
29
+ },
30
+ clear: ()=>{
31
+ if (!isBrowser()) return;
32
+ try {
33
+ getStorage().clear();
34
+ } catch {}
35
+ }
36
+ };
37
+ }
38
+ const safeLocalStorage = isBrowser() ? createSafeStorage(()=>window.localStorage) : noopStorage;
39
+ const safeSessionStorage = isBrowser() ? createSafeStorage(()=>window.sessionStorage) : noopStorage;
40
+ export { safeLocalStorage, safeSessionStorage };
@@ -0,0 +1,9 @@
1
+ import { useEffect } from 'react';
2
+ /**
3
+ * `useLayoutEffect` in the browser, `useEffect` on the server.
4
+ *
5
+ * Plain `useLayoutEffect` is a no-op during server rendering and logs a React warning
6
+ * ("useLayoutEffect does nothing on the server"). Use this instead anywhere theme code
7
+ * needs layout-effect timing on the client but must render cleanly on the server.
8
+ */
9
+ export declare const useIsomorphicLayoutEffect: typeof useEffect;
@@ -0,0 +1,4 @@
1
+ import { useEffect, useLayoutEffect } from "react";
2
+ import { isBrowser } from "./environment.js";
3
+ const useIsomorphicLayoutEffect = isBrowser() ? useLayoutEffect : useEffect;
4
+ export { useIsomorphicLayoutEffect };
package/dist/index.cjs CHANGED
@@ -1,11 +1,17 @@
1
1
  "use strict";
2
2
  var __webpack_modules__ = {
3
+ "./components/client-only": function(module) {
4
+ module.exports = require("./components/client-only.cjs");
5
+ },
3
6
  "./components/remote-boundary": function(module) {
4
7
  module.exports = require("./components/remote-boundary.cjs");
5
8
  },
6
9
  "./context/ecommerce-data-bridge": function(module) {
7
10
  module.exports = require("./context/ecommerce-data-bridge.cjs");
8
11
  },
12
+ "./context/use-host-location": function(module) {
13
+ module.exports = require("./context/use-host-location.cjs");
14
+ },
9
15
  "./types/actions": function(module) {
10
16
  module.exports = require("./types/actions.cjs");
11
17
  },
@@ -26,6 +32,15 @@ var __webpack_modules__ = {
26
32
  },
27
33
  "./types/product-card": function(module) {
28
34
  module.exports = require("./types/product-card.cjs");
35
+ },
36
+ "./utils/environment": function(module) {
37
+ module.exports = require("./utils/environment.cjs");
38
+ },
39
+ "./utils/safe-storage": function(module) {
40
+ module.exports = require("./utils/safe-storage.cjs");
41
+ },
42
+ "./utils/use-isomorphic-layout-effect": function(module) {
43
+ module.exports = require("./utils/use-isomorphic-layout-effect.cjs");
29
44
  }
30
45
  };
31
46
  var __webpack_module_cache__ = {};
@@ -71,58 +86,88 @@ function __webpack_require__(moduleId) {
71
86
  var __webpack_exports__ = {};
72
87
  (()=>{
73
88
  __webpack_require__.r(__webpack_exports__);
74
- var _components_remote_boundary__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./components/remote-boundary");
89
+ var _components_client_only__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./components/client-only");
90
+ var __WEBPACK_REEXPORT_OBJECT__ = {};
91
+ for(var __WEBPACK_IMPORT_KEY__ in _components_client_only__WEBPACK_IMPORTED_MODULE_0__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
92
+ return _components_client_only__WEBPACK_IMPORTED_MODULE_0__[key];
93
+ }).bind(0, __WEBPACK_IMPORT_KEY__);
94
+ __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
95
+ var _components_remote_boundary__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./components/remote-boundary");
96
+ var __WEBPACK_REEXPORT_OBJECT__ = {};
97
+ for(var __WEBPACK_IMPORT_KEY__ in _components_remote_boundary__WEBPACK_IMPORTED_MODULE_1__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
98
+ return _components_remote_boundary__WEBPACK_IMPORTED_MODULE_1__[key];
99
+ }).bind(0, __WEBPACK_IMPORT_KEY__);
100
+ __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
101
+ var _context_ecommerce_data_bridge__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__("./context/ecommerce-data-bridge");
102
+ var __WEBPACK_REEXPORT_OBJECT__ = {};
103
+ for(var __WEBPACK_IMPORT_KEY__ in _context_ecommerce_data_bridge__WEBPACK_IMPORTED_MODULE_2__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
104
+ return _context_ecommerce_data_bridge__WEBPACK_IMPORTED_MODULE_2__[key];
105
+ }).bind(0, __WEBPACK_IMPORT_KEY__);
106
+ __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
107
+ var _context_use_host_location__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__("./context/use-host-location");
108
+ var __WEBPACK_REEXPORT_OBJECT__ = {};
109
+ for(var __WEBPACK_IMPORT_KEY__ in _context_use_host_location__WEBPACK_IMPORTED_MODULE_3__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
110
+ return _context_use_host_location__WEBPACK_IMPORTED_MODULE_3__[key];
111
+ }).bind(0, __WEBPACK_IMPORT_KEY__);
112
+ __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
113
+ var _types_actions__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__("./types/actions");
114
+ var __WEBPACK_REEXPORT_OBJECT__ = {};
115
+ for(var __WEBPACK_IMPORT_KEY__ in _types_actions__WEBPACK_IMPORTED_MODULE_4__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
116
+ return _types_actions__WEBPACK_IMPORTED_MODULE_4__[key];
117
+ }).bind(0, __WEBPACK_IMPORT_KEY__);
118
+ __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
119
+ var _types_data__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__("./types/data");
75
120
  var __WEBPACK_REEXPORT_OBJECT__ = {};
76
- for(var __WEBPACK_IMPORT_KEY__ in _components_remote_boundary__WEBPACK_IMPORTED_MODULE_0__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
77
- return _components_remote_boundary__WEBPACK_IMPORTED_MODULE_0__[key];
121
+ for(var __WEBPACK_IMPORT_KEY__ in _types_data__WEBPACK_IMPORTED_MODULE_5__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
122
+ return _types_data__WEBPACK_IMPORTED_MODULE_5__[key];
78
123
  }).bind(0, __WEBPACK_IMPORT_KEY__);
79
124
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
80
- var _context_ecommerce_data_bridge__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./context/ecommerce-data-bridge");
125
+ var _types_ecommerce_extension__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__("./types/ecommerce-extension");
81
126
  var __WEBPACK_REEXPORT_OBJECT__ = {};
82
- for(var __WEBPACK_IMPORT_KEY__ in _context_ecommerce_data_bridge__WEBPACK_IMPORTED_MODULE_1__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
83
- return _context_ecommerce_data_bridge__WEBPACK_IMPORTED_MODULE_1__[key];
127
+ for(var __WEBPACK_IMPORT_KEY__ in _types_ecommerce_extension__WEBPACK_IMPORTED_MODULE_6__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
128
+ return _types_ecommerce_extension__WEBPACK_IMPORTED_MODULE_6__[key];
84
129
  }).bind(0, __WEBPACK_IMPORT_KEY__);
85
130
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
86
- var _types_actions__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__("./types/actions");
131
+ var _types_events__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__("./types/events");
87
132
  var __WEBPACK_REEXPORT_OBJECT__ = {};
88
- for(var __WEBPACK_IMPORT_KEY__ in _types_actions__WEBPACK_IMPORTED_MODULE_2__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
89
- return _types_actions__WEBPACK_IMPORTED_MODULE_2__[key];
133
+ for(var __WEBPACK_IMPORT_KEY__ in _types_events__WEBPACK_IMPORTED_MODULE_7__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
134
+ return _types_events__WEBPACK_IMPORTED_MODULE_7__[key];
90
135
  }).bind(0, __WEBPACK_IMPORT_KEY__);
91
136
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
92
- var _types_data__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__("./types/data");
137
+ var _types_filter_side_panel__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__("./types/filter-side-panel");
93
138
  var __WEBPACK_REEXPORT_OBJECT__ = {};
94
- for(var __WEBPACK_IMPORT_KEY__ in _types_data__WEBPACK_IMPORTED_MODULE_3__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
95
- return _types_data__WEBPACK_IMPORTED_MODULE_3__[key];
139
+ for(var __WEBPACK_IMPORT_KEY__ in _types_filter_side_panel__WEBPACK_IMPORTED_MODULE_8__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
140
+ return _types_filter_side_panel__WEBPACK_IMPORTED_MODULE_8__[key];
96
141
  }).bind(0, __WEBPACK_IMPORT_KEY__);
97
142
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
98
- var _types_ecommerce_extension__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__("./types/ecommerce-extension");
143
+ var _types_interface__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__("./types/interface");
99
144
  var __WEBPACK_REEXPORT_OBJECT__ = {};
100
- for(var __WEBPACK_IMPORT_KEY__ in _types_ecommerce_extension__WEBPACK_IMPORTED_MODULE_4__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
101
- return _types_ecommerce_extension__WEBPACK_IMPORTED_MODULE_4__[key];
145
+ for(var __WEBPACK_IMPORT_KEY__ in _types_interface__WEBPACK_IMPORTED_MODULE_9__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
146
+ return _types_interface__WEBPACK_IMPORTED_MODULE_9__[key];
102
147
  }).bind(0, __WEBPACK_IMPORT_KEY__);
103
148
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
104
- var _types_events__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__("./types/events");
149
+ var _types_product_card__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__("./types/product-card");
105
150
  var __WEBPACK_REEXPORT_OBJECT__ = {};
106
- for(var __WEBPACK_IMPORT_KEY__ in _types_events__WEBPACK_IMPORTED_MODULE_5__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
107
- return _types_events__WEBPACK_IMPORTED_MODULE_5__[key];
151
+ for(var __WEBPACK_IMPORT_KEY__ in _types_product_card__WEBPACK_IMPORTED_MODULE_10__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
152
+ return _types_product_card__WEBPACK_IMPORTED_MODULE_10__[key];
108
153
  }).bind(0, __WEBPACK_IMPORT_KEY__);
109
154
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
110
- var _types_filter_side_panel__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__("./types/filter-side-panel");
155
+ var _utils_environment__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__("./utils/environment");
111
156
  var __WEBPACK_REEXPORT_OBJECT__ = {};
112
- for(var __WEBPACK_IMPORT_KEY__ in _types_filter_side_panel__WEBPACK_IMPORTED_MODULE_6__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
113
- return _types_filter_side_panel__WEBPACK_IMPORTED_MODULE_6__[key];
157
+ for(var __WEBPACK_IMPORT_KEY__ in _utils_environment__WEBPACK_IMPORTED_MODULE_11__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
158
+ return _utils_environment__WEBPACK_IMPORTED_MODULE_11__[key];
114
159
  }).bind(0, __WEBPACK_IMPORT_KEY__);
115
160
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
116
- var _types_interface__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__("./types/interface");
161
+ var _utils_safe_storage__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__("./utils/safe-storage");
117
162
  var __WEBPACK_REEXPORT_OBJECT__ = {};
118
- for(var __WEBPACK_IMPORT_KEY__ in _types_interface__WEBPACK_IMPORTED_MODULE_7__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
119
- return _types_interface__WEBPACK_IMPORTED_MODULE_7__[key];
163
+ for(var __WEBPACK_IMPORT_KEY__ in _utils_safe_storage__WEBPACK_IMPORTED_MODULE_12__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
164
+ return _utils_safe_storage__WEBPACK_IMPORTED_MODULE_12__[key];
120
165
  }).bind(0, __WEBPACK_IMPORT_KEY__);
121
166
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
122
- var _types_product_card__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__("./types/product-card");
167
+ var _utils_use_isomorphic_layout_effect__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__("./utils/use-isomorphic-layout-effect");
123
168
  var __WEBPACK_REEXPORT_OBJECT__ = {};
124
- for(var __WEBPACK_IMPORT_KEY__ in _types_product_card__WEBPACK_IMPORTED_MODULE_8__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
125
- return _types_product_card__WEBPACK_IMPORTED_MODULE_8__[key];
169
+ for(var __WEBPACK_IMPORT_KEY__ in _utils_use_isomorphic_layout_effect__WEBPACK_IMPORTED_MODULE_13__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
170
+ return _utils_use_isomorphic_layout_effect__WEBPACK_IMPORTED_MODULE_13__[key];
126
171
  }).bind(0, __WEBPACK_IMPORT_KEY__);
127
172
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
128
173
  })();
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ export * from './components/client-only';
1
2
  export * from './components/remote-boundary';
2
3
  export * from './context/ecommerce-data-bridge';
4
+ export * from './context/use-host-location';
3
5
  export * from './types/actions';
4
6
  export * from './types/data';
5
7
  export * from './types/ecommerce-extension';
@@ -7,3 +9,6 @@ export * from './types/events';
7
9
  export * from './types/filter-side-panel';
8
10
  export * from './types/interface';
9
11
  export * from './types/product-card';
12
+ export * from './utils/environment';
13
+ export * from './utils/safe-storage';
14
+ export * from './utils/use-isomorphic-layout-effect';
@@ -1,10 +1,69 @@
1
1
  import type { Actions } from './actions';
2
- import type { Cart, DataLoaders, Dispensary, MenuContext, User } from './data';
2
+ import type { Brand, Cart, Category, Collection, DataLoaders, Dispensary, LoyaltyData, MenuContext, Product, Special, User } from './data';
3
+ /**
4
+ * The current request's URL info, provided by the host so theme code never needs
5
+ * `window.location` (which is unavailable during server rendering and, even when guarded
6
+ * with a `typeof window` check, causes a hydration mismatch since the server and the
7
+ * client's first render would otherwise disagree). Populated identically on the server
8
+ * and the client -- see `useHostLocation()`.
9
+ */
10
+ export type HostLocation = {
11
+ /** Pathname only, e.g. `/product/some-product` (no query string). */
12
+ path: string;
13
+ /** Raw query string, including the leading `?` if present, or `''`. */
14
+ search: string;
15
+ /** Request host/domain, when known. May be omitted in contexts where the host can't supply it. */
16
+ host?: string;
17
+ };
18
+ /**
19
+ * V2 commerce data: plain, already-resolved values instead of promise-returning loader
20
+ * functions (see {@link DataLoaders} / `dataLoaders`).
21
+ *
22
+ * Most `dataLoaders` fields wrap data the host has already fetched for its own
23
+ * (non-themed) rendering of the same page -- `dataLoaders.brands()` and
24
+ * `dataLoaders.categories()`, for example, are `Promise.resolve()`-wrapped values that
25
+ * are never actually asynchronous by the time a theme calls them. Forcing every theme
26
+ * to treat that data as async (via `useAsyncLoader` or hand-rolled promise handling) is
27
+ * unnecessary ceremony, and it's specifically what stood in the way of real SSR: an
28
+ * `useEffect`-based fetch can't produce data during server rendering, but a value that's
29
+ * already sitting in memory doesn't need an effect at all.
30
+ *
31
+ * Prefer this over `dataLoaders` in new theme code. Fields are `undefined` until the
32
+ * host has resolved them for the current page -- check for presence rather than
33
+ * awaiting. See the SSR readiness guide for the full rationale and the `dataLoaders`
34
+ * migration path.
35
+ */
36
+ export type CommerceDataV2 = {
37
+ brands?: Brand[];
38
+ categories?: Category[];
39
+ collections?: Collection[];
40
+ /** Only populated on a PDP. */
41
+ product?: Product | null;
42
+ products?: Product[];
43
+ specials?: Special[];
44
+ /** All dispensary locations of the chain. */
45
+ locations?: Dispensary[];
46
+ loyalty?: LoyaltyData | null;
47
+ };
3
48
  export type CommerceComponentsDataInterface = {
4
49
  actions: Actions;
5
50
  cart?: Cart;
51
+ /**
52
+ * V2 synchronous commerce data. Prefer this over `dataLoaders` in new theme code --
53
+ * see {@link CommerceDataV2}.
54
+ */
55
+ data?: CommerceDataV2;
56
+ /**
57
+ * @deprecated Prefer `data` (see {@link CommerceDataV2}) where the field you need is
58
+ * available there. Kept fully functional for backward compatibility -- not being
59
+ * removed, just superseded for new theme code. `integrationValue` has no `data`
60
+ * equivalent (it's a genuinely dynamic, caller-parameterized lookup) and stays here.
61
+ */
6
62
  dataLoaders: DataLoaders;
63
+ /** Note: this is the current dispensary location, not URL info -- see `request` for that. */
7
64
  location?: Dispensary;
8
65
  menuContext: MenuContext;
66
+ /** Current request URL info (path/search/host). See {@link HostLocation}. */
67
+ request?: HostLocation;
9
68
  user?: User;
10
69
  };
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ isBrowser: ()=>isBrowser,
28
+ isServer: ()=>isServer
29
+ });
30
+ const isBrowser = ()=>'undefined' != typeof window && 'undefined' != typeof document;
31
+ const isServer = ()=>!isBrowser();
32
+ exports.isBrowser = __webpack_exports__.isBrowser;
33
+ exports.isServer = __webpack_exports__.isServer;
34
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
35
+ "isBrowser",
36
+ "isServer"
37
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
38
+ Object.defineProperty(exports, '__esModule', {
39
+ value: true
40
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Canonical environment check. Use this instead of ad-hoc `typeof window !== 'undefined'`
3
+ * checks scattered through theme code -- a single source of truth is easier to audit
4
+ * (and to lint for) than dozens of hand-rolled variants.
5
+ */
6
+ export declare const isBrowser: () => boolean;
7
+ /** Inverse of {@link isBrowser}, provided so call sites can read either direction naturally. */
8
+ export declare const isServer: () => boolean;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ safeLocalStorage: ()=>safeLocalStorage,
28
+ safeSessionStorage: ()=>safeSessionStorage
29
+ });
30
+ const external_environment_cjs_namespaceObject = require("./environment.cjs");
31
+ const noopStorage = {
32
+ getItem: ()=>null,
33
+ setItem: ()=>{},
34
+ removeItem: ()=>{},
35
+ clear: ()=>{}
36
+ };
37
+ function createSafeStorage(getStorage) {
38
+ return {
39
+ getItem: (key)=>{
40
+ if (!(0, external_environment_cjs_namespaceObject.isBrowser)()) return null;
41
+ try {
42
+ return getStorage().getItem(key);
43
+ } catch {
44
+ return null;
45
+ }
46
+ },
47
+ setItem: (key, value)=>{
48
+ if (!(0, external_environment_cjs_namespaceObject.isBrowser)()) return;
49
+ try {
50
+ getStorage().setItem(key, value);
51
+ } catch {}
52
+ },
53
+ removeItem: (key)=>{
54
+ if (!(0, external_environment_cjs_namespaceObject.isBrowser)()) return;
55
+ try {
56
+ getStorage().removeItem(key);
57
+ } catch {}
58
+ },
59
+ clear: ()=>{
60
+ if (!(0, external_environment_cjs_namespaceObject.isBrowser)()) return;
61
+ try {
62
+ getStorage().clear();
63
+ } catch {}
64
+ }
65
+ };
66
+ }
67
+ const safeLocalStorage = (0, external_environment_cjs_namespaceObject.isBrowser)() ? createSafeStorage(()=>window.localStorage) : noopStorage;
68
+ const safeSessionStorage = (0, external_environment_cjs_namespaceObject.isBrowser)() ? createSafeStorage(()=>window.sessionStorage) : noopStorage;
69
+ exports.safeLocalStorage = __webpack_exports__.safeLocalStorage;
70
+ exports.safeSessionStorage = __webpack_exports__.safeSessionStorage;
71
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
72
+ "safeLocalStorage",
73
+ "safeSessionStorage"
74
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
75
+ Object.defineProperty(exports, '__esModule', {
76
+ value: true
77
+ });
@@ -0,0 +1,14 @@
1
+ export type SafeStorage = {
2
+ getItem: (key: string) => string | null;
3
+ setItem: (key: string, value: string) => void;
4
+ removeItem: (key: string) => void;
5
+ clear: () => void;
6
+ };
7
+ /**
8
+ * `window.localStorage`, but safe to call unconditionally during server rendering
9
+ * (returns/no-ops instead of throwing `window is not defined`) and safe in
10
+ * environments where storage access itself can throw (private browsing, quota, policy).
11
+ */
12
+ export declare const safeLocalStorage: SafeStorage;
13
+ /** `window.sessionStorage` counterpart to {@link safeLocalStorage}. */
14
+ export declare const safeSessionStorage: SafeStorage;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ useIsomorphicLayoutEffect: ()=>useIsomorphicLayoutEffect
28
+ });
29
+ const external_react_namespaceObject = require("react");
30
+ const external_environment_cjs_namespaceObject = require("./environment.cjs");
31
+ const useIsomorphicLayoutEffect = (0, external_environment_cjs_namespaceObject.isBrowser)() ? external_react_namespaceObject.useLayoutEffect : external_react_namespaceObject.useEffect;
32
+ exports.useIsomorphicLayoutEffect = __webpack_exports__.useIsomorphicLayoutEffect;
33
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
34
+ "useIsomorphicLayoutEffect"
35
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
36
+ Object.defineProperty(exports, '__esModule', {
37
+ value: true
38
+ });
@@ -0,0 +1,9 @@
1
+ import { useEffect } from 'react';
2
+ /**
3
+ * `useLayoutEffect` in the browser, `useEffect` on the server.
4
+ *
5
+ * Plain `useLayoutEffect` is a no-op during server rendering and logs a React warning
6
+ * ("useLayoutEffect does nothing on the server"). Use this instead anywhere theme code
7
+ * needs layout-effect timing on the client but must render cleanly on the server.
8
+ */
9
+ export declare const useIsomorphicLayoutEffect: typeof useEffect;
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "url": "git+https://github.com/GetDutchie/ecommerce-extensions-sdk.git",
10
10
  "directory": "packages/sdk"
11
11
  },
12
- "version": "0.36.0",
12
+ "version": "0.36.1",
13
13
  "license": "MIT",
14
14
  "type": "module",
15
15
  "module": "./dist/esm/index.js",
@@ -41,6 +41,7 @@
41
41
  "@testing-library/react": "^12.1.5",
42
42
  "@testing-library/react-hooks": "^8.0.1",
43
43
  "@types/react": "^17.0.0",
44
+ "@types/react-dom": "^17.0.0",
44
45
  "jsdom": "^26.1.0",
45
46
  "react": "^17.0.0",
46
47
  "react-dom": "^17.0.0",