@growupsell/react 1.0.0-alpha.1 → 1.0.0-alpha.3
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 +13 -2
- package/dist/index.d.ts +98 -1
- package/dist/index.js +356 -0
- package/dist/index.js.map +1 -1
- package/package.json +15 -4
package/README.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
React adapter for @growupsell/commerce: provider and hooks. Works on web and React Native.
|
|
4
4
|
|
|
5
|
-
Peer: `react ^18 || ^19`.
|
|
5
|
+
Peer: `react ^18 || ^19`. One entry point, ESM only, a `"use client"` bundle (safe to import from Next.js App Router).
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```tsx
|
|
8
|
+
import { GrowupsellProvider, useCart } from '@growupsell/react';
|
|
9
|
+
import { cartItemCount } from '@growupsell/commerce/stores';
|
|
10
|
+
|
|
11
|
+
<GrowupsellProvider options={{ baseURL, businessId: business.id, platform: 'website' }}>
|
|
12
|
+
<App />
|
|
13
|
+
</GrowupsellProvider>;
|
|
14
|
+
|
|
15
|
+
const count = useCart(cartItemCount);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Guide: [docs/guides/react.md](https://github.com/tenthspire/gs-frontend-lib/blob/dev/docs/guides/react.md) (setup in Vite, Next.js, Node and Expo; hooks and selectors; per-screen hooks; events; server rendering; storage).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,99 @@
|
|
|
1
|
+
import { Store, GrowupsellStores, GrowupsellStoresOptions, AuthState, BusinessState, CartState, CatalogState, CheckoutState, Identity, ReferralState, SessionState, ThemeConfigState, WishlistState, GrowupsellEvent, ListingInput, ListingState, ListingStore, ProductDetailInput, ProductDetailState, ProductDetailStore, SearchSuggestionsOptions, SearchSuggestionsState, SearchSuggestionsStore } from '@growupsell/commerce/stores';
|
|
2
|
+
export { AuthState, BusinessState, CartState, CatalogState, CheckoutState, GrowupsellEvent, GrowupsellStores, GrowupsellStoresOptions, Identity, ListingInput, ListingState, ListingStore, ProductDetailInput, ProductDetailState, ProductDetailStore, ReferralState, SearchSuggestionsOptions, SearchSuggestionsState, SearchSuggestionsStore, SessionState, Store, ThemeConfigState, WishlistState } from '@growupsell/commerce/stores';
|
|
3
|
+
import { ReactNode, ReactElement } from 'react';
|
|
4
|
+
import { CheckoutPreview, PaymentOptions } from '@growupsell/commerce/rules';
|
|
5
|
+
export { CheckoutPreview, PaymentOptions } from '@growupsell/commerce/rules';
|
|
1
6
|
|
|
2
|
-
|
|
7
|
+
type EqualityFn<T> = (a: T, b: T) => boolean;
|
|
8
|
+
/** Re-renders only when `isEqual(previous, next)` is false. Default `Object.is`. */
|
|
9
|
+
declare function useStore<T>(store: Store<T>): T;
|
|
10
|
+
declare function useStore<T, S>(store: Store<T>, selector: (state: T) => S, isEqual?: EqualityFn<S>): S;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* One level deep, `Object.is` per value. For selectors that build an object or array: plain
|
|
14
|
+
* objects (same keys), arrays (same items in order), `Date` (same time), `Map` (same keys and
|
|
15
|
+
* values) and `Set` (same members). Any other object (a class instance, a RegExp) is equal only
|
|
16
|
+
* to itself.
|
|
17
|
+
*/
|
|
18
|
+
declare function shallowEqual<T>(a: T, b: T): boolean;
|
|
19
|
+
|
|
20
|
+
type GrowupsellProviderProps = {
|
|
21
|
+
/** An instance the app created and owns: never disposed by the provider. */
|
|
22
|
+
stores: GrowupsellStores;
|
|
23
|
+
options?: never;
|
|
24
|
+
/** Call `stores.hydrate()` after the first commit. Default true. */
|
|
25
|
+
hydrate?: boolean;
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
} | {
|
|
28
|
+
/**
|
|
29
|
+
* Read once per mount: the provider creates the instance on the first render and disposes
|
|
30
|
+
* it after a real unmount. To switch business, change the provider's `key`. Function
|
|
31
|
+
* options (`onUnauthorized`, `onError`, `merchantToken`) always call the latest prop.
|
|
32
|
+
*/
|
|
33
|
+
options: GrowupsellStoresOptions;
|
|
34
|
+
stores?: never;
|
|
35
|
+
hydrate?: boolean;
|
|
36
|
+
children?: ReactNode;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Holds one `GrowupsellStores` per mount, hydrates it after the first commit and, when it created
|
|
40
|
+
* the instance, disposes it after a real unmount. Always renders its children.
|
|
41
|
+
*/
|
|
42
|
+
declare function GrowupsellProvider(props: GrowupsellProviderProps): ReactElement;
|
|
43
|
+
/**
|
|
44
|
+
* The instance, or null outside a provider: for shared UI (header, footer) that must still render
|
|
45
|
+
* when the app could not mount one (e.g. the business did not resolve). Render the parts that use
|
|
46
|
+
* other hooks only when it is not null.
|
|
47
|
+
*/
|
|
48
|
+
declare function useGrowupsell(options: {
|
|
49
|
+
optional: true;
|
|
50
|
+
}): GrowupsellStores | null;
|
|
51
|
+
/** The instance: `useGrowupsell().cart.add(...)`. Throws outside a provider. Stable for the mount. */
|
|
52
|
+
declare function useGrowupsell(): GrowupsellStores;
|
|
53
|
+
|
|
54
|
+
/** A hook over one store of the instance: the whole snapshot, or a memoised selection. */
|
|
55
|
+
interface StoreHook<T> {
|
|
56
|
+
(): T;
|
|
57
|
+
<S>(selector: (state: T) => S, isEqual?: EqualityFn<S>): S;
|
|
58
|
+
}
|
|
59
|
+
declare const useSession: StoreHook<SessionState>;
|
|
60
|
+
declare const useAuth: StoreHook<AuthState>;
|
|
61
|
+
declare const useCart: StoreHook<CartState>;
|
|
62
|
+
declare const useWishlist: StoreHook<WishlistState>;
|
|
63
|
+
declare const useCheckout: StoreHook<CheckoutState>;
|
|
64
|
+
declare const useBusiness: StoreHook<BusinessState>;
|
|
65
|
+
declare const useCatalog: StoreHook<CatalogState>;
|
|
66
|
+
declare const useThemeConfig: StoreHook<ThemeConfigState>;
|
|
67
|
+
declare const useReferral: StoreHook<ReferralState>;
|
|
68
|
+
declare const useIdentity: StoreHook<Identity>;
|
|
69
|
+
/** False on the server and in the first client render; true once stored logins are read. */
|
|
70
|
+
declare function useHydrated(): boolean;
|
|
71
|
+
|
|
72
|
+
/** Subscribes after mount (never on the server); always calls the latest `listener`. */
|
|
73
|
+
declare function useGrowupsellEvent<K extends GrowupsellEvent['type']>(type: K, listener: (event: Extract<GrowupsellEvent, {
|
|
74
|
+
type: K;
|
|
75
|
+
}>) => void): void;
|
|
76
|
+
|
|
77
|
+
/** `checkoutPreviewFor(cart, checkout, auth)`; re-renders only when the preview changes (shallow). */
|
|
78
|
+
declare function useCheckoutPreview(): CheckoutPreview | null;
|
|
79
|
+
/** `paymentOptionsFor(cart, checkout, auth, business.features)`; same rule. */
|
|
80
|
+
declare function usePaymentOptions(): PaymentOptions | null;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* One listing per mount. A later `input.query` change calls `applyQuery` once (no search when
|
|
84
|
+
* equal), with `input.categoryId` as its category when the query names none, so the app can keep
|
|
85
|
+
* the category in its path. A later `input.categoryId` change alone calls `setCategory` (which
|
|
86
|
+
* clears the old category's filters), unless the query names its own category. `pageSize` is
|
|
87
|
+
* read once.
|
|
88
|
+
*/
|
|
89
|
+
declare function useListing(input?: ListingInput): [ListingState, ListingStore];
|
|
90
|
+
declare function useListing<S>(input: ListingInput | undefined, selector: (state: ListingState) => S, isEqual?: EqualityFn<S>): [S, ListingStore];
|
|
91
|
+
/** Keyed on `productId`: a new id creates a new store; `selectedVariantIds` and `initial` are read at creation. */
|
|
92
|
+
declare function useProductDetail(input: ProductDetailInput): [ProductDetailState, ProductDetailStore];
|
|
93
|
+
declare function useProductDetail<S>(input: ProductDetailInput, selector: (state: ProductDetailState) => S, isEqual?: EqualityFn<S>): [S, ProductDetailStore];
|
|
94
|
+
/** One per mount; `options` read once. */
|
|
95
|
+
declare function useSearchSuggestions(options?: SearchSuggestionsOptions): [SearchSuggestionsState, SearchSuggestionsStore];
|
|
96
|
+
declare function useSearchSuggestions<S>(options: SearchSuggestionsOptions | undefined, selector: (state: SearchSuggestionsState) => S, isEqual?: EqualityFn<S>): [S, SearchSuggestionsStore];
|
|
97
|
+
|
|
98
|
+
export { GrowupsellProvider, shallowEqual, useAuth, useBusiness, useCart, useCatalog, useCheckout, useCheckoutPreview, useGrowupsell, useGrowupsellEvent, useHydrated, useIdentity, useListing, usePaymentOptions, useProductDetail, useReferral, useSearchSuggestions, useSession, useStore, useThemeConfig, useWishlist };
|
|
99
|
+
export type { EqualityFn, GrowupsellProviderProps, StoreHook };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,358 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
|
|
3
|
+
// src/useStore.ts
|
|
4
|
+
import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
5
|
+
function useStore(store, selector, isEqual = Object.is) {
|
|
6
|
+
const committed = useRef(null);
|
|
7
|
+
const [getSelection, getServerSelection] = useMemo(() => {
|
|
8
|
+
const memoised = (read) => {
|
|
9
|
+
let last = null;
|
|
10
|
+
return () => {
|
|
11
|
+
const snapshot = read();
|
|
12
|
+
if (last && Object.is(last.snapshot, snapshot)) return last.selection;
|
|
13
|
+
const next = selector ? selector(snapshot) : snapshot;
|
|
14
|
+
const previous = last ?? (committed.current?.store === store ? committed.current : null);
|
|
15
|
+
const selection2 = previous && isEqual(previous.selection, next) ? previous.selection : next;
|
|
16
|
+
last = { snapshot, selection: selection2 };
|
|
17
|
+
return selection2;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
return [memoised(store.getSnapshot), memoised(store.getServerSnapshot)];
|
|
21
|
+
}, [store, selector, isEqual]);
|
|
22
|
+
const selection = useSyncExternalStore(store.subscribe, getSelection, getServerSelection);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
committed.current = { store, selection };
|
|
25
|
+
});
|
|
26
|
+
return selection;
|
|
27
|
+
}
|
|
28
|
+
var select = useStore;
|
|
29
|
+
|
|
30
|
+
// src/shallowEqual.ts
|
|
31
|
+
var isPlain = (value) => {
|
|
32
|
+
const proto = Object.getPrototypeOf(value);
|
|
33
|
+
return proto === Object.prototype || proto === null;
|
|
34
|
+
};
|
|
35
|
+
function shallowEqual(a, b) {
|
|
36
|
+
if (Object.is(a, b)) return true;
|
|
37
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
38
|
+
const left = a;
|
|
39
|
+
const right = b;
|
|
40
|
+
if (Object.getPrototypeOf(left) !== Object.getPrototypeOf(right)) {
|
|
41
|
+
if (!isPlain(left) || !isPlain(right)) return false;
|
|
42
|
+
} else if (left instanceof Date) {
|
|
43
|
+
return left.getTime() === right.getTime();
|
|
44
|
+
} else if (left instanceof Map) {
|
|
45
|
+
const other = right;
|
|
46
|
+
if (left.size !== other.size) return false;
|
|
47
|
+
for (const [key, value] of left) {
|
|
48
|
+
if (!other.has(key) || !Object.is(value, other.get(key))) return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
} else if (left instanceof Set) {
|
|
52
|
+
const other = right;
|
|
53
|
+
if (left.size !== other.size) return false;
|
|
54
|
+
for (const value of left) if (!other.has(value)) return false;
|
|
55
|
+
return true;
|
|
56
|
+
} else if (!Array.isArray(left) && !isPlain(left)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const leftRecord = left;
|
|
60
|
+
const rightRecord = right;
|
|
61
|
+
const keys = Object.keys(leftRecord);
|
|
62
|
+
if (keys.length !== Object.keys(rightRecord).length) return false;
|
|
63
|
+
return keys.every(
|
|
64
|
+
(key) => Object.prototype.hasOwnProperty.call(rightRecord, key) && Object.is(leftRecord[key], rightRecord[key])
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/provider.tsx
|
|
69
|
+
import {
|
|
70
|
+
createGrowupsellStores
|
|
71
|
+
} from "@growupsell/commerce/stores";
|
|
72
|
+
import {
|
|
73
|
+
createContext,
|
|
74
|
+
useContext,
|
|
75
|
+
useEffect as useEffect3,
|
|
76
|
+
useInsertionEffect as useInsertionEffect2,
|
|
77
|
+
useRef as useRef3
|
|
78
|
+
} from "react";
|
|
79
|
+
|
|
80
|
+
// src/host.ts
|
|
81
|
+
var host = globalThis;
|
|
82
|
+
function rethrowAsync(error) {
|
|
83
|
+
host.setTimeout(() => {
|
|
84
|
+
throw error;
|
|
85
|
+
}, 0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/useOwned.ts
|
|
89
|
+
import { useEffect as useEffect2, useInsertionEffect, useReducer, useRef as useRef2, useState } from "react";
|
|
90
|
+
var unmounted = /* @__PURE__ */ new Set();
|
|
91
|
+
var owners = /* @__PURE__ */ new WeakMap();
|
|
92
|
+
var pruneAt = 64;
|
|
93
|
+
var weakly = (value) => typeof WeakRef === "function" ? new WeakRef(value) : { deref: () => value };
|
|
94
|
+
function own(value, key, dispose) {
|
|
95
|
+
const entry = {
|
|
96
|
+
value,
|
|
97
|
+
key,
|
|
98
|
+
dispose,
|
|
99
|
+
mounted: false,
|
|
100
|
+
disposed: false,
|
|
101
|
+
timer: void 0,
|
|
102
|
+
ref: null
|
|
103
|
+
};
|
|
104
|
+
if (typeof value === "object" && value !== null) {
|
|
105
|
+
entry.ref = weakly(value);
|
|
106
|
+
owners.set(value, entry);
|
|
107
|
+
unmounted.add(entry.ref);
|
|
108
|
+
if (unmounted.size >= pruneAt) {
|
|
109
|
+
for (const ref of unmounted) if (ref.deref() === void 0) unmounted.delete(ref);
|
|
110
|
+
pruneAt = Math.max(64, unmounted.size * 2);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return entry;
|
|
114
|
+
}
|
|
115
|
+
function release(entry) {
|
|
116
|
+
if (entry.timer !== void 0) host.clearTimeout(entry.timer);
|
|
117
|
+
entry.timer = void 0;
|
|
118
|
+
if (entry.disposed) return;
|
|
119
|
+
entry.disposed = true;
|
|
120
|
+
entry.dispose(entry.value);
|
|
121
|
+
}
|
|
122
|
+
function disposeUnmounted() {
|
|
123
|
+
for (const ref of unmounted) {
|
|
124
|
+
unmounted.delete(ref);
|
|
125
|
+
const value = ref.deref();
|
|
126
|
+
const entry = value === void 0 ? void 0 : owners.get(value);
|
|
127
|
+
if (entry && !entry.mounted) release(entry);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
var sameKey = (a, b) => a.length === b.length && a.every((part, i) => Object.is(part, b[i]));
|
|
131
|
+
function useOwned(create, key, dispose) {
|
|
132
|
+
const [state, setState] = useState(() => own(create(), key, dispose));
|
|
133
|
+
let entry = state;
|
|
134
|
+
if (!sameKey(entry.key, key)) {
|
|
135
|
+
entry = own(create(), key, dispose);
|
|
136
|
+
setState(entry);
|
|
137
|
+
}
|
|
138
|
+
const latestCreate = useRef2(create);
|
|
139
|
+
const [, rerender] = useReducer((n) => n + 1, 0);
|
|
140
|
+
useInsertionEffect(() => {
|
|
141
|
+
latestCreate.current = create;
|
|
142
|
+
});
|
|
143
|
+
useInsertionEffect(() => {
|
|
144
|
+
entry.mounted = true;
|
|
145
|
+
if (entry.timer !== void 0) host.clearTimeout(entry.timer);
|
|
146
|
+
entry.timer = void 0;
|
|
147
|
+
if (entry.ref) unmounted.delete(entry.ref);
|
|
148
|
+
return () => {
|
|
149
|
+
if (!entry.disposed && entry.timer === void 0) {
|
|
150
|
+
entry.timer = host.setTimeout(() => release(entry), 0);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}, [entry]);
|
|
154
|
+
useEffect2(() => {
|
|
155
|
+
if (entry.disposed) {
|
|
156
|
+
entry.value = latestCreate.current();
|
|
157
|
+
entry.disposed = false;
|
|
158
|
+
rerender();
|
|
159
|
+
}
|
|
160
|
+
disposeUnmounted();
|
|
161
|
+
}, [entry]);
|
|
162
|
+
return entry.value;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/provider.tsx
|
|
166
|
+
import { jsx } from "react/jsx-runtime";
|
|
167
|
+
var GrowupsellContext = createContext(null);
|
|
168
|
+
var IDENTITY_OPTIONS = ["baseURL", "businessId", "platform"];
|
|
169
|
+
var ONCE = [];
|
|
170
|
+
var disposeInstance = (stores) => stores?.dispose();
|
|
171
|
+
function forwarding(options, latest) {
|
|
172
|
+
return {
|
|
173
|
+
...options,
|
|
174
|
+
onUnauthorized: (event) => latest.current?.onUnauthorized?.(event),
|
|
175
|
+
onError: (error) => (latest.current?.onError ?? rethrowAsync)(error),
|
|
176
|
+
merchantToken: () => latest.current?.merchantToken?.() ?? null
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function GrowupsellProvider(props) {
|
|
180
|
+
const { hydrate = true, children } = props;
|
|
181
|
+
const latest = useRef3(props.options);
|
|
182
|
+
useInsertionEffect2(() => {
|
|
183
|
+
latest.current = props.options;
|
|
184
|
+
});
|
|
185
|
+
const created = useOwned(
|
|
186
|
+
() => props.stores || !props.options ? null : createGrowupsellStores(forwarding(props.options, latest)),
|
|
187
|
+
ONCE,
|
|
188
|
+
disposeInstance
|
|
189
|
+
);
|
|
190
|
+
const stores = props.stores ?? created;
|
|
191
|
+
if (!stores) {
|
|
192
|
+
throw new Error("<GrowupsellProvider> needs either `stores` or `options`.");
|
|
193
|
+
}
|
|
194
|
+
const initial = useRef3(props.options);
|
|
195
|
+
const warned = useRef3(false);
|
|
196
|
+
useEffect3(() => {
|
|
197
|
+
const first = initial.current;
|
|
198
|
+
const now = props.options;
|
|
199
|
+
if (!first || !now || warned.current) return;
|
|
200
|
+
if (IDENTITY_OPTIONS.some((key) => !Object.is(first[key], now[key]))) {
|
|
201
|
+
warned.current = true;
|
|
202
|
+
host.console.error(
|
|
203
|
+
"<GrowupsellProvider>: baseURL, businessId and platform are read once. Change the provider's `key` to switch them."
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
const hydratedFor = useRef3(null);
|
|
208
|
+
useEffect3(() => {
|
|
209
|
+
if (!hydrate || hydratedFor.current === stores) return;
|
|
210
|
+
hydratedFor.current = stores;
|
|
211
|
+
void stores.hydrate();
|
|
212
|
+
}, [stores, hydrate]);
|
|
213
|
+
return /* @__PURE__ */ jsx(GrowupsellContext.Provider, { value: stores, children });
|
|
214
|
+
}
|
|
215
|
+
function useGrowupsell(options) {
|
|
216
|
+
const stores = useContext(GrowupsellContext);
|
|
217
|
+
if (!stores && !options?.optional) {
|
|
218
|
+
throw new Error("useGrowupsell must be used inside <GrowupsellProvider>");
|
|
219
|
+
}
|
|
220
|
+
return stores;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/storeHooks.ts
|
|
224
|
+
function storeHook(pick) {
|
|
225
|
+
return function useStoreOfInstance(selector, isEqual) {
|
|
226
|
+
return select(pick(useGrowupsell()), selector, isEqual);
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
var useSession = storeHook((s) => s.session);
|
|
230
|
+
var useAuth = storeHook((s) => s.auth);
|
|
231
|
+
var useCart = storeHook((s) => s.cart);
|
|
232
|
+
var useWishlist = storeHook((s) => s.wishlist);
|
|
233
|
+
var useCheckout = storeHook((s) => s.checkout);
|
|
234
|
+
var useBusiness = storeHook((s) => s.business);
|
|
235
|
+
var useCatalog = storeHook((s) => s.catalog);
|
|
236
|
+
var useThemeConfig = storeHook((s) => s.themeConfig);
|
|
237
|
+
var useReferral = storeHook((s) => s.referral);
|
|
238
|
+
var useIdentity = storeHook((s) => s.identity);
|
|
239
|
+
function useHydrated() {
|
|
240
|
+
return useStore(useGrowupsell().hydrated);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/events.ts
|
|
244
|
+
import { useEffect as useEffect4, useInsertionEffect as useInsertionEffect3, useRef as useRef4 } from "react";
|
|
245
|
+
function useGrowupsellEvent(type, listener) {
|
|
246
|
+
const stores = useGrowupsell();
|
|
247
|
+
const latest = useRef4(listener);
|
|
248
|
+
useInsertionEffect3(() => {
|
|
249
|
+
latest.current = listener;
|
|
250
|
+
});
|
|
251
|
+
useEffect4(() => stores.events.on(type, (event) => latest.current(event)), [stores, type]);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/derived.ts
|
|
255
|
+
import {
|
|
256
|
+
checkoutPreviewFor,
|
|
257
|
+
paymentOptionsFor
|
|
258
|
+
} from "@growupsell/commerce/stores";
|
|
259
|
+
import { useMemo as useMemo2 } from "react";
|
|
260
|
+
function combine(sources, compute) {
|
|
261
|
+
const memo = (read) => {
|
|
262
|
+
let last = null;
|
|
263
|
+
return () => {
|
|
264
|
+
const inputs = sources.map(read);
|
|
265
|
+
if (last && inputs.every((input, i) => Object.is(input, last.inputs[i]))) return last.value;
|
|
266
|
+
last = { inputs, value: compute(...inputs) };
|
|
267
|
+
return last.value;
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
return {
|
|
271
|
+
getSnapshot: memo((store) => store.getSnapshot()),
|
|
272
|
+
getServerSnapshot: memo((store) => store.getServerSnapshot()),
|
|
273
|
+
subscribe(listener) {
|
|
274
|
+
const offs = sources.map((store) => store.subscribe(listener));
|
|
275
|
+
return () => offs.forEach((off) => off());
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function useDerived(build) {
|
|
280
|
+
const stores = useGrowupsell();
|
|
281
|
+
const derived = useMemo2(() => build(stores), [build, stores]);
|
|
282
|
+
return useStore(derived, (value) => value, shallowEqual);
|
|
283
|
+
}
|
|
284
|
+
var previewStore = (s) => combine([s.cart, s.checkout, s.auth], checkoutPreviewFor);
|
|
285
|
+
var paymentOptionsStore = (s) => combine(
|
|
286
|
+
[s.cart, s.checkout, s.auth, s.business],
|
|
287
|
+
(cart, checkout, auth, business) => paymentOptionsFor(cart, checkout, auth, business.features)
|
|
288
|
+
);
|
|
289
|
+
function useCheckoutPreview() {
|
|
290
|
+
return useDerived(previewStore);
|
|
291
|
+
}
|
|
292
|
+
function usePaymentOptions() {
|
|
293
|
+
return useDerived(paymentOptionsStore);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/screens.ts
|
|
297
|
+
import { parseListingQuery } from "@growupsell/commerce/rules";
|
|
298
|
+
import { useEffect as useEffect5, useRef as useRef5 } from "react";
|
|
299
|
+
var disposeStore = (store) => store.dispose();
|
|
300
|
+
function useListing(input, selector, isEqual) {
|
|
301
|
+
const stores = useGrowupsell();
|
|
302
|
+
const store = useOwned(() => stores.createListing(input), [stores], disposeStore);
|
|
303
|
+
const queryKey = JSON.stringify(input?.query ?? null);
|
|
304
|
+
const categoryId = input?.categoryId ?? null;
|
|
305
|
+
const applied = useRef5({ store, queryKey, categoryId });
|
|
306
|
+
useEffect5(() => {
|
|
307
|
+
const last = applied.current;
|
|
308
|
+
applied.current = { store, queryKey, categoryId };
|
|
309
|
+
if (last.store !== store) return;
|
|
310
|
+
const query = input?.query ?? {};
|
|
311
|
+
const ownCategory = parseListingQuery(query).categoryId !== null;
|
|
312
|
+
if (last.queryKey !== queryKey) {
|
|
313
|
+
const asIs = ownCategory || categoryId === null;
|
|
314
|
+
store.applyQuery(asIs ? query : { ...query, category: String(categoryId) });
|
|
315
|
+
} else if (last.categoryId !== categoryId && !ownCategory) {
|
|
316
|
+
store.setCategory(categoryId);
|
|
317
|
+
}
|
|
318
|
+
}, [store, queryKey, categoryId]);
|
|
319
|
+
return [select(store, selector, isEqual), store];
|
|
320
|
+
}
|
|
321
|
+
function useProductDetail(input, selector, isEqual) {
|
|
322
|
+
const stores = useGrowupsell();
|
|
323
|
+
const store = useOwned(
|
|
324
|
+
() => stores.createProductDetail(input),
|
|
325
|
+
[stores, input.productId],
|
|
326
|
+
disposeStore
|
|
327
|
+
);
|
|
328
|
+
return [select(store, selector, isEqual), store];
|
|
329
|
+
}
|
|
330
|
+
function useSearchSuggestions(options, selector, isEqual) {
|
|
331
|
+
const stores = useGrowupsell();
|
|
332
|
+
const store = useOwned(() => stores.createSearchSuggestions(options), [stores], disposeStore);
|
|
333
|
+
return [select(store, selector, isEqual), store];
|
|
334
|
+
}
|
|
335
|
+
export {
|
|
336
|
+
GrowupsellProvider,
|
|
337
|
+
shallowEqual,
|
|
338
|
+
useAuth,
|
|
339
|
+
useBusiness,
|
|
340
|
+
useCart,
|
|
341
|
+
useCatalog,
|
|
342
|
+
useCheckout,
|
|
343
|
+
useCheckoutPreview,
|
|
344
|
+
useGrowupsell,
|
|
345
|
+
useGrowupsellEvent,
|
|
346
|
+
useHydrated,
|
|
347
|
+
useIdentity,
|
|
348
|
+
useListing,
|
|
349
|
+
usePaymentOptions,
|
|
350
|
+
useProductDetail,
|
|
351
|
+
useReferral,
|
|
352
|
+
useSearchSuggestions,
|
|
353
|
+
useSession,
|
|
354
|
+
useStore,
|
|
355
|
+
useThemeConfig,
|
|
356
|
+
useWishlist
|
|
357
|
+
};
|
|
2
358
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/useStore.ts","../src/shallowEqual.ts","../src/provider.tsx","../src/host.ts","../src/useOwned.ts","../src/storeHooks.ts","../src/events.ts","../src/derived.ts","../src/screens.ts"],"sourcesContent":["import type { Store } from '@growupsell/commerce/stores';\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\n\nexport type EqualityFn<T> = (a: T, b: T) => boolean;\n\ntype Selector<T, S> = (state: T) => S;\n\n/** What the last commit of a hook instance rendered. Written only in an effect. */\ninterface Committed<T, S> {\n store: Store<T>;\n selection: S;\n}\n\n/** Re-renders only when `isEqual(previous, next)` is false. Default `Object.is`. */\nexport function useStore<T>(store: Store<T>): T;\nexport function useStore<T, S>(\n store: Store<T>,\n selector: (state: T) => S,\n isEqual?: EqualityFn<S>,\n): S;\nexport function useStore<T, S>(\n store: Store<T>,\n selector?: Selector<T, S>,\n isEqual: EqualityFn<S> = Object.is,\n): S {\n const committed = useRef<Committed<T, S> | null>(null);\n\n // One memo per [store, selector, isEqual] (React's `useSyncExternalStoreWithSelector`\n // pattern): a render React throws away also throws its memo away, so it cannot change what\n // the committed tree compares against. The memo reuses its selection for the same snapshot,\n // and the previous selection (the committed one first) when `isEqual` says the new one equals\n // it. That keeps the value `useSyncExternalStore` sees stable, so inline selectors never loop.\n const [getSelection, getServerSelection] = useMemo(() => {\n const memoised = (read: () => T) => {\n let last: { snapshot: T; selection: S } | null = null;\n return () => {\n const snapshot = read();\n if (last && Object.is(last.snapshot, snapshot)) return last.selection;\n const next = selector ? selector(snapshot) : (snapshot as unknown as S);\n const previous = last ?? (committed.current?.store === store ? committed.current : null);\n const selection = previous && isEqual(previous.selection, next) ? previous.selection : next;\n last = { snapshot, selection };\n return selection;\n };\n };\n return [memoised(store.getSnapshot), memoised(store.getServerSnapshot)];\n }, [store, selector, isEqual]);\n\n const selection = useSyncExternalStore(store.subscribe, getSelection, getServerSelection);\n useEffect(() => {\n committed.current = { store, selection };\n });\n return selection;\n}\n\n/** `useStore` with an optional selector, for hooks whose own overloads type the result. */\nexport const select = useStore as <T, S>(\n store: Store<T>,\n selector?: (state: T) => S,\n isEqual?: EqualityFn<S>,\n) => S;\n","const isPlain = (value: object) => {\n const proto: unknown = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n};\n\n/**\n * One level deep, `Object.is` per value. For selectors that build an object or array: plain\n * objects (same keys), arrays (same items in order), `Date` (same time), `Map` (same keys and\n * values) and `Set` (same members). Any other object (a class instance, a RegExp) is equal only\n * to itself.\n */\nexport function shallowEqual<T>(a: T, b: T): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;\n const left: object = a;\n const right: object = b;\n if (Object.getPrototypeOf(left) !== Object.getPrototypeOf(right)) {\n if (!isPlain(left) || !isPlain(right)) return false;\n } else if (left instanceof Date) {\n return left.getTime() === (right as Date).getTime();\n } else if (left instanceof Map) {\n const other = right as Map<unknown, unknown>;\n if (left.size !== other.size) return false;\n for (const [key, value] of left) {\n if (!other.has(key) || !Object.is(value, other.get(key))) return false;\n }\n return true;\n } else if (left instanceof Set) {\n const other = right as Set<unknown>;\n if (left.size !== other.size) return false;\n for (const value of left) if (!other.has(value)) return false;\n return true;\n } else if (!Array.isArray(left) && !isPlain(left)) {\n return false;\n }\n const leftRecord = left as Record<string, unknown>;\n const rightRecord = right as Record<string, unknown>;\n const keys = Object.keys(leftRecord);\n if (keys.length !== Object.keys(rightRecord).length) return false;\n return keys.every(\n (key) =>\n Object.prototype.hasOwnProperty.call(rightRecord, key) &&\n Object.is(leftRecord[key], rightRecord[key]),\n );\n}\n","import {\n createGrowupsellStores,\n type GrowupsellStores,\n type GrowupsellStoresOptions,\n} from '@growupsell/commerce/stores';\nimport {\n createContext,\n useContext,\n useEffect,\n useInsertionEffect,\n useRef,\n type ReactElement,\n type ReactNode,\n type RefObject,\n} from 'react';\nimport { host, rethrowAsync } from './host';\nimport { useOwned } from './useOwned';\n\nexport type GrowupsellProviderProps =\n | {\n /** An instance the app created and owns: never disposed by the provider. */\n stores: GrowupsellStores;\n options?: never;\n /** Call `stores.hydrate()` after the first commit. Default true. */\n hydrate?: boolean;\n children?: ReactNode;\n }\n | {\n /**\n * Read once per mount: the provider creates the instance on the first render and disposes\n * it after a real unmount. To switch business, change the provider's `key`. Function\n * options (`onUnauthorized`, `onError`, `merchantToken`) always call the latest prop.\n */\n options: GrowupsellStoresOptions;\n stores?: never;\n hydrate?: boolean;\n children?: ReactNode;\n };\n\nconst GrowupsellContext = createContext<GrowupsellStores | null>(null);\n\n/** Options that identify the instance: changing them needs a new provider (`key`). */\nconst IDENTITY_OPTIONS = ['baseURL', 'businessId', 'platform'] as const;\n\n/** The provider creates at most one instance per mount. */\nconst ONCE: readonly unknown[] = [];\nconst disposeInstance = (stores: GrowupsellStores | null) => stores?.dispose();\n\n/** Replaces the function options with forwarders to the latest props. */\nfunction forwarding(\n options: GrowupsellStoresOptions,\n latest: RefObject<GrowupsellStoresOptions | undefined>,\n): GrowupsellStoresOptions {\n return {\n ...options,\n onUnauthorized: (event) => latest.current?.onUnauthorized?.(event),\n onError: (error) => (latest.current?.onError ?? rethrowAsync)(error),\n merchantToken: () => latest.current?.merchantToken?.() ?? null,\n };\n}\n\n/**\n * Holds one `GrowupsellStores` per mount, hydrates it after the first commit and, when it created\n * the instance, disposes it after a real unmount. Always renders its children.\n */\nexport function GrowupsellProvider(props: GrowupsellProviderProps): ReactElement {\n const { hydrate = true, children } = props;\n const latest = useRef<GrowupsellStoresOptions | undefined>(props.options);\n useInsertionEffect(() => {\n latest.current = props.options;\n });\n\n // Created during the first render: creation does no I/O, so it is safe on the server too.\n // Disposed after a real unmount; an instance created in a render React threw away is disposed\n // at the next commit (`useOwned`).\n const created = useOwned(\n () =>\n props.stores || !props.options\n ? null\n : createGrowupsellStores(forwarding(props.options, latest)),\n ONCE,\n disposeInstance,\n );\n const stores = props.stores ?? created;\n if (!stores) {\n throw new Error('<GrowupsellProvider> needs either `stores` or `options`.');\n }\n\n const initial = useRef(props.options);\n const warned = useRef(false);\n useEffect(() => {\n const first = initial.current;\n const now = props.options;\n if (!first || !now || warned.current) return;\n if (IDENTITY_OPTIONS.some((key) => !Object.is(first[key], now[key]))) {\n warned.current = true;\n host.console.error(\n \"<GrowupsellProvider>: baseURL, businessId and platform are read once. Change the provider's `key` to switch them.\",\n );\n }\n });\n\n // After the first commit, never on the server. Once per instance (StrictMode re-runs effects).\n const hydratedFor = useRef<GrowupsellStores | null>(null);\n useEffect(() => {\n if (!hydrate || hydratedFor.current === stores) return;\n hydratedFor.current = stores;\n void stores.hydrate();\n }, [stores, hydrate]);\n\n return <GrowupsellContext.Provider value={stores}>{children}</GrowupsellContext.Provider>;\n}\n\n/**\n * The instance, or null outside a provider: for shared UI (header, footer) that must still render\n * when the app could not mount one (e.g. the business did not resolve). Render the parts that use\n * other hooks only when it is not null.\n */\nexport function useGrowupsell(options: { optional: true }): GrowupsellStores | null;\n/** The instance: `useGrowupsell().cart.add(...)`. Throws outside a provider. Stable for the mount. */\nexport function useGrowupsell(): GrowupsellStores;\nexport function useGrowupsell(options?: { optional?: boolean }): GrowupsellStores | null {\n const stores = useContext(GrowupsellContext);\n if (!stores && !options?.optional) {\n throw new Error('useGrowupsell must be used inside <GrowupsellProvider>');\n }\n return stores;\n}\n","/** The globals the adapter uses, through `globalThis`: it compiles without DOM or Node types (ADR-009). */\nexport const host = globalThis as unknown as {\n setTimeout(handler: () => void, ms: number): unknown;\n clearTimeout(id: unknown): void;\n console: { error(...data: unknown[]): void };\n};\n\n/** The default for listener and storage errors, as in commerce: rethrown on a later tick. */\nexport function rethrowAsync(error: unknown): void {\n host.setTimeout(() => {\n throw error;\n }, 0);\n}\n","/**\n * `useOwned`: a value a component creates during render and disposes when it is done with it\n * (the provider's instance, per-screen stores). Creating in render keeps the first render, on\n * the server too, able to show the value (creation does no I/O). What makes that safe:\n *\n * - **Mounted** is tracked in an insertion effect. Insertion effects run once per real mount,\n * also in a hidden `<Activity>`, and their cleanup runs once per real unmount: StrictMode's\n * effect double-invoke and `<Activity mode=\"hidden\">` do not touch them (layout and passive\n * effects are disconnected and run again instead). So hiding a subtree keeps its values.\n * - **Created but never mounted** (React threw the render away: StrictMode's second call of a\n * state initializer, a suspended or interrupted render) is disposed at the next commit of any\n * `useOwned`. Those values are held weakly, so the server, which never commits, keeps none.\n * - **Disposed while mounted** (a sweep that ran before a slow render committed, in another\n * root) is replaced in the passive effect, with a re-render.\n * - A real unmount or a new key disposes the old value after a tick, cancelled if the same value\n * mounts again meanwhile (Fast Refresh runs the cleanup and the setup again).\n */\nimport { useEffect, useInsertionEffect, useReducer, useRef, useState } from 'react';\nimport { host } from './host';\n\ninterface Owned<T> {\n value: T;\n readonly key: readonly unknown[];\n readonly dispose: (value: T) => void;\n mounted: boolean;\n disposed: boolean;\n /** The dispose an unmount scheduled; undefined when none is pending. */\n timer: unknown;\n /** Its entry in `unmounted` while not mounted. */\n ref: WeakReference | null;\n}\n\ninterface WeakReference {\n deref(): object | undefined;\n}\n\n/** Values created in render that no commit has mounted yet. Weak: a server render keeps none. */\nconst unmounted = new Set<WeakReference>();\nconst owners = new WeakMap<object, Owned<unknown>>();\nlet pruneAt = 64;\n\nconst weakly = (value: object): WeakReference =>\n typeof WeakRef === 'function' ? new WeakRef(value) : { deref: () => value };\n\nfunction own<T>(value: T, key: readonly unknown[], dispose: (value: T) => void): Owned<T> {\n const entry: Owned<T> = {\n value,\n key,\n dispose,\n mounted: false,\n disposed: false,\n timer: undefined,\n ref: null,\n };\n if (typeof value === 'object' && value !== null) {\n entry.ref = weakly(value);\n owners.set(value, entry as Owned<unknown>);\n unmounted.add(entry.ref);\n if (unmounted.size >= pruneAt) {\n for (const ref of unmounted) if (ref.deref() === undefined) unmounted.delete(ref);\n pruneAt = Math.max(64, unmounted.size * 2);\n }\n }\n return entry;\n}\n\nfunction release(entry: Owned<unknown>): void {\n if (entry.timer !== undefined) host.clearTimeout(entry.timer);\n entry.timer = undefined;\n if (entry.disposed) return;\n entry.disposed = true;\n entry.dispose(entry.value);\n}\n\n/** Disposes every value created in a render that never committed. */\nexport function disposeUnmounted(): void {\n for (const ref of unmounted) {\n unmounted.delete(ref);\n const value = ref.deref();\n const entry = value === undefined ? undefined : owners.get(value);\n if (entry && !entry.mounted) release(entry);\n }\n}\n\nconst sameKey = (a: readonly unknown[], b: readonly unknown[]) =>\n a.length === b.length && a.every((part, i) => Object.is(part, b[i]));\n\n/**\n * One value per `key` (compared part by part), created with `create` during render and disposed\n * with `dispose` after the component is done with it. A new key creates the new value in the\n * same render; the old one is disposed after the commit.\n */\nexport function useOwned<T>(\n create: () => T,\n key: readonly unknown[],\n dispose: (value: T) => void,\n): T {\n const [state, setState] = useState(() => own(create(), key, dispose));\n let entry = state;\n if (!sameKey(entry.key, key)) {\n // React's \"adjust state when a prop changes\": re-renders at once with the new value.\n entry = own(create(), key, dispose);\n setState(entry);\n }\n\n const latestCreate = useRef(create);\n const [, rerender] = useReducer((n: number) => n + 1, 0);\n\n useInsertionEffect(() => {\n latestCreate.current = create;\n });\n\n useInsertionEffect(() => {\n entry.mounted = true;\n if (entry.timer !== undefined) host.clearTimeout(entry.timer);\n entry.timer = undefined;\n if (entry.ref) unmounted.delete(entry.ref);\n return () => {\n if (!entry.disposed && entry.timer === undefined) {\n entry.timer = host.setTimeout(() => release(entry as Owned<unknown>), 0);\n }\n };\n }, [entry]);\n\n useEffect(() => {\n if (entry.disposed) {\n entry.value = latestCreate.current();\n entry.disposed = false;\n rerender();\n }\n // Every insertion effect of this commit has run: what is still unmounted never will be.\n disposeUnmounted();\n }, [entry]);\n\n return entry.value;\n}\n","import type {\n AuthState,\n BusinessState,\n CartState,\n CatalogState,\n CheckoutState,\n GrowupsellStores,\n Identity,\n ReferralState,\n SessionState,\n Store,\n ThemeConfigState,\n WishlistState,\n} from '@growupsell/commerce/stores';\nimport { useGrowupsell } from './provider';\nimport { select, useStore, type EqualityFn } from './useStore';\n\n/** A hook over one store of the instance: the whole snapshot, or a memoised selection. */\nexport interface StoreHook<T> {\n (): T;\n <S>(selector: (state: T) => S, isEqual?: EqualityFn<S>): S;\n}\n\nfunction storeHook<T>(pick: (stores: GrowupsellStores) => Store<T>): StoreHook<T> {\n return function useStoreOfInstance<S>(selector?: (state: T) => S, isEqual?: EqualityFn<S>) {\n return select(pick(useGrowupsell()), selector, isEqual);\n } as StoreHook<T>;\n}\n\nexport const useSession: StoreHook<SessionState> = storeHook((s) => s.session);\nexport const useAuth: StoreHook<AuthState> = storeHook((s) => s.auth);\nexport const useCart: StoreHook<CartState> = storeHook((s) => s.cart);\nexport const useWishlist: StoreHook<WishlistState> = storeHook((s) => s.wishlist);\nexport const useCheckout: StoreHook<CheckoutState> = storeHook((s) => s.checkout);\nexport const useBusiness: StoreHook<BusinessState> = storeHook((s) => s.business);\nexport const useCatalog: StoreHook<CatalogState> = storeHook((s) => s.catalog);\nexport const useThemeConfig: StoreHook<ThemeConfigState> = storeHook((s) => s.themeConfig);\nexport const useReferral: StoreHook<ReferralState> = storeHook((s) => s.referral);\nexport const useIdentity: StoreHook<Identity> = storeHook((s) => s.identity);\n\n/** False on the server and in the first client render; true once stored logins are read. */\nexport function useHydrated(): boolean {\n return useStore(useGrowupsell().hydrated);\n}\n","import type { GrowupsellEvent } from '@growupsell/commerce/stores';\nimport { useEffect, useInsertionEffect, useRef } from 'react';\nimport { useGrowupsell } from './provider';\n\n/** Subscribes after mount (never on the server); always calls the latest `listener`. */\nexport function useGrowupsellEvent<K extends GrowupsellEvent['type']>(\n type: K,\n listener: (event: Extract<GrowupsellEvent, { type: K }>) => void,\n): void {\n const stores = useGrowupsell();\n const latest = useRef(listener);\n useInsertionEffect(() => {\n latest.current = listener;\n });\n useEffect(() => stores.events.on(type, (event) => latest.current(event)), [stores, type]);\n}\n","import type { CheckoutPreview, PaymentOptions } from '@growupsell/commerce/rules';\nimport {\n checkoutPreviewFor,\n paymentOptionsFor,\n type GrowupsellStores,\n type Store,\n} from '@growupsell/commerce/stores';\nimport { useMemo } from 'react';\nimport { useGrowupsell } from './provider';\nimport { shallowEqual } from './shallowEqual';\nimport { useStore } from './useStore';\n\ntype Snapshots<Sources extends readonly Store<unknown>[]> = {\n [I in keyof Sources]: Sources[I] extends Store<infer T> ? T : never;\n};\n\n/**\n * A read-only `Store` over several stores: `compute` runs again only when one of their snapshots\n * changed. The server snapshot is computed from their server snapshots. No logic of its own:\n * `compute` is a commerce selector.\n */\nfunction combine<Sources extends readonly Store<unknown>[], R>(\n sources: Sources,\n compute: (...snapshots: Snapshots<Sources>) => R,\n): Store<R> {\n const memo = (read: (store: Store<unknown>) => unknown) => {\n let last: { inputs: unknown[]; value: R } | null = null;\n return () => {\n const inputs = sources.map(read);\n if (last && inputs.every((input, i) => Object.is(input, last!.inputs[i]))) return last.value;\n last = { inputs, value: compute(...(inputs as Snapshots<Sources>)) };\n return last.value;\n };\n };\n return {\n getSnapshot: memo((store) => store.getSnapshot()),\n getServerSnapshot: memo((store) => store.getServerSnapshot()),\n subscribe(listener) {\n const offs = sources.map((store) => store.subscribe(listener));\n return () => offs.forEach((off) => off());\n },\n };\n}\n\nfunction useDerived<R>(build: (stores: GrowupsellStores) => Store<R>): R {\n const stores = useGrowupsell();\n // One derived store per instance (`build` is a module-level constant).\n const derived = useMemo(() => build(stores), [build, stores]);\n return useStore(derived, (value) => value, shallowEqual);\n}\n\nconst previewStore = (s: GrowupsellStores) =>\n combine([s.cart, s.checkout, s.auth] as const, checkoutPreviewFor);\n\nconst paymentOptionsStore = (s: GrowupsellStores) =>\n combine([s.cart, s.checkout, s.auth, s.business] as const, (cart, checkout, auth, business) =>\n paymentOptionsFor(cart, checkout, auth, business.features),\n );\n\n/** `checkoutPreviewFor(cart, checkout, auth)`; re-renders only when the preview changes (shallow). */\nexport function useCheckoutPreview(): CheckoutPreview | null {\n return useDerived(previewStore);\n}\n\n/** `paymentOptionsFor(cart, checkout, auth, business.features)`; same rule. */\nexport function usePaymentOptions(): PaymentOptions | null {\n return useDerived(paymentOptionsStore);\n}\n","import { parseListingQuery } from '@growupsell/commerce/rules';\nimport type {\n ListingInput,\n ListingState,\n ListingStore,\n ProductDetailInput,\n ProductDetailState,\n ProductDetailStore,\n SearchSuggestionsOptions,\n SearchSuggestionsState,\n SearchSuggestionsStore,\n} from '@growupsell/commerce/stores';\nimport { useEffect, useRef } from 'react';\nimport { useGrowupsell } from './provider';\nimport { useOwned } from './useOwned';\nimport { select, type EqualityFn } from './useStore';\n\n/**\n * Per-screen stores are created during render (no I/O, so the server renders them too, never\n * subscribed and collected with the request's instance) and disposed after unmount (`useOwned`).\n */\nconst disposeStore = (store: { dispose(): void }) => store.dispose();\n\n/**\n * One listing per mount. A later `input.query` change calls `applyQuery` once (no search when\n * equal), with `input.categoryId` as its category when the query names none, so the app can keep\n * the category in its path. A later `input.categoryId` change alone calls `setCategory` (which\n * clears the old category's filters), unless the query names its own category. `pageSize` is\n * read once.\n */\nexport function useListing(input?: ListingInput): [ListingState, ListingStore];\nexport function useListing<S>(\n input: ListingInput | undefined,\n selector: (state: ListingState) => S,\n isEqual?: EqualityFn<S>,\n): [S, ListingStore];\nexport function useListing<S>(\n input?: ListingInput,\n selector?: (state: ListingState) => S,\n isEqual?: EqualityFn<S>,\n): [S | ListingState, ListingStore] {\n const stores = useGrowupsell();\n const store = useOwned(() => stores.createListing(input), [stores], disposeStore);\n\n // Route changes after mount, applied once per commit from both inputs together; the values at\n // creation are already in the store. `input.query` is read through `queryKey`: a new but\n // equal object is no change.\n const queryKey = JSON.stringify(input?.query ?? null);\n const categoryId = input?.categoryId ?? null;\n const applied = useRef({ store, queryKey, categoryId });\n useEffect(() => {\n const last = applied.current;\n applied.current = { store, queryKey, categoryId };\n // A new store was created from this input.\n if (last.store !== store) return;\n const query = input?.query ?? {};\n // A category in the query wins; else the app keeps it in its path (`categoryId`).\n const ownCategory = parseListingQuery(query).categoryId !== null;\n if (last.queryKey !== queryKey) {\n // The query is the whole criteria: its filters belong to the category it comes with.\n const asIs = ownCategory || categoryId === null;\n store.applyQuery(asIs ? query : { ...query, category: String(categoryId) });\n } else if (last.categoryId !== categoryId && !ownCategory) {\n // Same query, other category: its filters belonged to the old one (ADR-025).\n store.setCategory(categoryId);\n }\n }, [store, queryKey, categoryId]);\n\n return [select(store, selector, isEqual), store];\n}\n\n/** Keyed on `productId`: a new id creates a new store; `selectedVariantIds` and `initial` are read at creation. */\nexport function useProductDetail(\n input: ProductDetailInput,\n): [ProductDetailState, ProductDetailStore];\nexport function useProductDetail<S>(\n input: ProductDetailInput,\n selector: (state: ProductDetailState) => S,\n isEqual?: EqualityFn<S>,\n): [S, ProductDetailStore];\nexport function useProductDetail<S>(\n input: ProductDetailInput,\n selector?: (state: ProductDetailState) => S,\n isEqual?: EqualityFn<S>,\n): [S | ProductDetailState, ProductDetailStore] {\n const stores = useGrowupsell();\n const store = useOwned(\n () => stores.createProductDetail(input),\n [stores, input.productId],\n disposeStore,\n );\n return [select(store, selector, isEqual), store];\n}\n\n/** One per mount; `options` read once. */\nexport function useSearchSuggestions(\n options?: SearchSuggestionsOptions,\n): [SearchSuggestionsState, SearchSuggestionsStore];\nexport function useSearchSuggestions<S>(\n options: SearchSuggestionsOptions | undefined,\n selector: (state: SearchSuggestionsState) => S,\n isEqual?: EqualityFn<S>,\n): [S, SearchSuggestionsStore];\nexport function useSearchSuggestions<S>(\n options?: SearchSuggestionsOptions,\n selector?: (state: SearchSuggestionsState) => S,\n isEqual?: EqualityFn<S>,\n): [S | SearchSuggestionsState, SearchSuggestionsStore] {\n const stores = useGrowupsell();\n const store = useOwned(() => stores.createSearchSuggestions(options), [stores], disposeStore);\n return [select(store, selector, isEqual), store];\n}\n"],"mappings":"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","names":["selection","useEffect","useInsertionEffect","useRef","useEffect","useRef","useRef","useEffect","useRef","useInsertionEffect","useEffect","useEffect","useInsertionEffect","useRef","useRef","useInsertionEffect","useEffect","useMemo","useMemo","useEffect","useRef","useRef","useEffect"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@growupsell/react",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.3",
|
|
4
4
|
"description": "React adapter for @growupsell/commerce: provider and hooks. Works on web and React Native.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -20,16 +20,27 @@
|
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "tsup",
|
|
23
|
-
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
24
|
-
"test": "vitest run
|
|
23
|
+
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
|
|
24
|
+
"test": "vitest run"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@growupsell/commerce": "1.0.0-alpha.
|
|
27
|
+
"@growupsell/commerce": "1.0.0-alpha.3"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"react": "^18.0.0 || ^19.0.0"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@gs-internal/adapter-conformance": "0.0.0",
|
|
37
|
+
"@gs-internal/mock-backend": "0.0.0",
|
|
38
|
+
"@testing-library/dom": "^10.4.2",
|
|
39
|
+
"@testing-library/react": "^16.3.3",
|
|
40
|
+
"@types/react": "~19.3.0",
|
|
41
|
+
"@types/react-dom": "~19.3.0",
|
|
42
|
+
"jsdom": "^30.1.3",
|
|
43
|
+
"react": "19.3.0",
|
|
44
|
+
"react-dom": "19.3.0"
|
|
34
45
|
}
|
|
35
46
|
}
|