@shopbb/helium 0.2.0 → 0.3.0-alpha.2

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.
Files changed (56) hide show
  1. package/dist/components/AddToCartButton.d.ts +41 -0
  2. package/dist/components/AddToCartButton.d.ts.map +1 -0
  3. package/dist/components/AddToCartButton.js +63 -0
  4. package/dist/components/AddToCartButton.js.map +1 -0
  5. package/dist/components/AnalyticsProvider.d.ts +106 -0
  6. package/dist/components/AnalyticsProvider.d.ts.map +1 -0
  7. package/dist/components/AnalyticsProvider.js +135 -0
  8. package/dist/components/AnalyticsProvider.js.map +1 -0
  9. package/dist/components/CartLineQuantityAdjustButton.d.ts +40 -0
  10. package/dist/components/CartLineQuantityAdjustButton.d.ts.map +1 -0
  11. package/dist/components/CartLineQuantityAdjustButton.js +58 -0
  12. package/dist/components/CartLineQuantityAdjustButton.js.map +1 -0
  13. package/dist/components/CartProvider.d.ts +111 -0
  14. package/dist/components/CartProvider.d.ts.map +1 -0
  15. package/dist/components/CartProvider.js +263 -0
  16. package/dist/components/CartProvider.js.map +1 -0
  17. package/dist/components/Image.d.ts +39 -0
  18. package/dist/components/Image.d.ts.map +1 -0
  19. package/dist/components/Image.js +28 -0
  20. package/dist/components/Image.js.map +1 -0
  21. package/dist/components/Money.d.ts +41 -0
  22. package/dist/components/Money.d.ts.map +1 -0
  23. package/dist/components/Money.js +53 -0
  24. package/dist/components/Money.js.map +1 -0
  25. package/dist/components/ProductOptionsProvider.d.ts +70 -0
  26. package/dist/components/ProductOptionsProvider.d.ts.map +1 -0
  27. package/dist/components/ProductOptionsProvider.js +93 -0
  28. package/dist/components/ProductOptionsProvider.js.map +1 -0
  29. package/dist/components/ProductPrice.d.ts +28 -0
  30. package/dist/components/ProductPrice.d.ts.map +1 -0
  31. package/dist/components/ProductPrice.js +10 -0
  32. package/dist/components/ProductPrice.js.map +1 -0
  33. package/dist/components/ShopProvider.d.ts +49 -0
  34. package/dist/components/ShopProvider.d.ts.map +1 -0
  35. package/dist/components/ShopProvider.js +62 -0
  36. package/dist/components/ShopProvider.js.map +1 -0
  37. package/dist/components/VariantSelector.d.ts +80 -0
  38. package/dist/components/VariantSelector.d.ts.map +1 -0
  39. package/dist/components/VariantSelector.js +87 -0
  40. package/dist/components/VariantSelector.js.map +1 -0
  41. package/dist/components/index.d.ts +41 -0
  42. package/dist/components/index.d.ts.map +1 -0
  43. package/dist/components/index.js +33 -0
  44. package/dist/components/index.js.map +1 -0
  45. package/package.json +12 -4
  46. package/src/components/AddToCartButton.tsx +101 -0
  47. package/src/components/AnalyticsProvider.tsx +175 -0
  48. package/src/components/CartLineQuantityAdjustButton.tsx +119 -0
  49. package/src/components/CartProvider.tsx +378 -0
  50. package/src/components/Image.tsx +93 -0
  51. package/src/components/Money.tsx +112 -0
  52. package/src/components/ProductOptionsProvider.tsx +149 -0
  53. package/src/components/ProductPrice.tsx +61 -0
  54. package/src/components/ShopProvider.tsx +86 -0
  55. package/src/components/VariantSelector.tsx +148 -0
  56. package/src/components/index.ts +71 -0
@@ -0,0 +1,263 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * <CartProvider> + useCart()
4
+ *
5
+ * 接管 cart 全生命周期:
6
+ * - 自动从 cookie 读取 cart_id 并 fetch
7
+ * - 不存在则懒创建
8
+ * - linesAdd / linesUpdate / linesRemove 全部带 optimistic update + 失败回滚
9
+ * - subscribe(callback) 给外部(如 Analytics)订阅 cart 变化
10
+ *
11
+ * 用法:
12
+ * <ShopProvider {...}>
13
+ * <CartProvider>
14
+ * <App />
15
+ * </CartProvider>
16
+ * </ShopProvider>
17
+ *
18
+ * const { cart, status, linesAdd, linesUpdate, linesRemove } = useCart();
19
+ *
20
+ * 注意:必须套在 <ShopProvider> 内(依赖它的 storefrontAccessToken + apiUrl)。
21
+ */
22
+ import * as React from 'react';
23
+ import { useShop } from './ShopProvider';
24
+ const CartContext = React.createContext(null);
25
+ // ============================================================
26
+ // GraphQL
27
+ // ============================================================
28
+ const CART_FRAGMENT = /* GraphQL */ `
29
+ fragment CartParts on Cart {
30
+ id totalQuantity checkoutUrl
31
+ lines(first: 100) {
32
+ nodes {
33
+ id quantity
34
+ merchandise {
35
+ ... on ProductVariant {
36
+ id title
37
+ image { url altText width height }
38
+ price { amount currencyCode }
39
+ product { title handle }
40
+ }
41
+ }
42
+ cost { totalAmount { amount currencyCode } }
43
+ }
44
+ }
45
+ cost {
46
+ subtotalAmount { amount currencyCode }
47
+ totalAmount { amount currencyCode }
48
+ }
49
+ }
50
+ `;
51
+ const Q_GET_CART = CART_FRAGMENT + `
52
+ query GetCart($id: ID!) { cart(id: $id) { ...CartParts } }
53
+ `;
54
+ const M_CART_CREATE = CART_FRAGMENT + `
55
+ mutation Create($input: CartInput) {
56
+ cartCreate(input: $input) { cart { ...CartParts } userErrors { field message code } }
57
+ }
58
+ `;
59
+ const M_LINES_ADD = CART_FRAGMENT + `
60
+ mutation Add($cartId: ID!, $lines: [CartLineInput!]!) {
61
+ cartLinesAdd(cartId: $cartId, lines: $lines) { cart { ...CartParts } userErrors { field message code } }
62
+ }
63
+ `;
64
+ const M_LINES_UPDATE = CART_FRAGMENT + `
65
+ mutation Update($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
66
+ cartLinesUpdate(cartId: $cartId, lines: $lines) { cart { ...CartParts } userErrors { field message code } }
67
+ }
68
+ `;
69
+ const M_LINES_REMOVE = CART_FRAGMENT + `
70
+ mutation Remove($cartId: ID!, $lineIds: [ID!]!) {
71
+ cartLinesRemove(cartId: $cartId, lineIds: $lineIds) { cart { ...CartParts } userErrors { field message code } }
72
+ }
73
+ `;
74
+ // ============================================================
75
+ // cookie cart id
76
+ // ============================================================
77
+ const COOKIE_NAME = 'cart';
78
+ function readCartIdFromCookie() {
79
+ if (typeof document === 'undefined')
80
+ return null;
81
+ const m = document.cookie.match(new RegExp('(?:^|; )' + COOKIE_NAME + '=([^;]+)'));
82
+ if (!m)
83
+ return null;
84
+ return `gid://shopbb/Cart/${decodeURIComponent(m[1])}`;
85
+ }
86
+ function saveCartIdToCookie(cartGid) {
87
+ if (typeof document === 'undefined')
88
+ return;
89
+ const id = cartGid.replace(/^gid:\/\/shopbb\/Cart\//, '');
90
+ const oneYear = 365 * 24 * 60 * 60;
91
+ document.cookie = `${COOKIE_NAME}=${encodeURIComponent(id)}; path=/; max-age=${oneYear}; SameSite=Lax`;
92
+ }
93
+ export function CartProvider({ children, fetchOnMount = true }) {
94
+ const shop = useShop();
95
+ const [cart, setCart] = React.useState(null);
96
+ const [status, setStatus] = React.useState('uninitialized');
97
+ const [error, setError] = React.useState(null);
98
+ const listenersRef = React.useRef(new Set());
99
+ const gql = React.useCallback(async (query, variables) => {
100
+ const res = await fetch(shop.apiUrl, {
101
+ method: 'POST',
102
+ headers: {
103
+ 'Content-Type': 'application/json',
104
+ 'X-Storefront-Access-Token': shop.storefrontAccessToken,
105
+ },
106
+ body: JSON.stringify({ query, variables }),
107
+ credentials: 'include',
108
+ });
109
+ const json = await res.json();
110
+ if (json.errors)
111
+ throw new Error(json.errors[0]?.message || 'GraphQL error');
112
+ return json.data;
113
+ }, [shop.apiUrl, shop.storefrontAccessToken]);
114
+ const notifyListeners = React.useCallback((c) => {
115
+ for (const fn of listenersRef.current)
116
+ fn(c);
117
+ }, []);
118
+ // 应用新 cart 数据(state + 订阅者)
119
+ const applyCart = React.useCallback((next) => {
120
+ setCart(next);
121
+ notifyListeners(next);
122
+ }, [notifyListeners]);
123
+ // 保证有 cart_id,没有就创建
124
+ const ensureCartId = React.useCallback(async () => {
125
+ const cookieCartId = readCartIdFromCookie();
126
+ if (cookieCartId)
127
+ return cookieCartId;
128
+ const data = await gql(M_CART_CREATE, { input: { lines: [] } });
129
+ if (data.cartCreate.userErrors.length > 0) {
130
+ throw new Error(data.cartCreate.userErrors[0].message);
131
+ }
132
+ const newCart = data.cartCreate.cart;
133
+ saveCartIdToCookie(newCart.id);
134
+ applyCart(newCart);
135
+ return newCart.id;
136
+ }, [gql, applyCart]);
137
+ // 初始拉取
138
+ const refetch = React.useCallback(async () => {
139
+ setStatus('loading');
140
+ try {
141
+ const cookieCartId = readCartIdFromCookie();
142
+ if (!cookieCartId) {
143
+ applyCart(null);
144
+ setStatus('idle');
145
+ return;
146
+ }
147
+ const data = await gql(Q_GET_CART, { id: cookieCartId });
148
+ applyCart(data.cart);
149
+ setStatus('idle');
150
+ }
151
+ catch (err) {
152
+ setError(err?.message ?? String(err));
153
+ setStatus('error');
154
+ }
155
+ }, [gql, applyCart]);
156
+ React.useEffect(() => {
157
+ if (fetchOnMount && status === 'uninitialized') {
158
+ void refetch();
159
+ }
160
+ }, [fetchOnMount, status, refetch]);
161
+ // optimistic 包装
162
+ const runMutation = React.useCallback(async (optimisticCart, mutator) => {
163
+ const prev = cart;
164
+ if (optimisticCart)
165
+ applyCart(optimisticCart);
166
+ setStatus('updating');
167
+ try {
168
+ const result = await mutator();
169
+ if (result.userErrors && result.userErrors.length > 0) {
170
+ throw new Error(result.userErrors[0].message);
171
+ }
172
+ applyCart(result.cart);
173
+ setStatus('idle');
174
+ setError(null);
175
+ }
176
+ catch (err) {
177
+ applyCart(prev); // 回滚
178
+ setError(err?.message ?? String(err));
179
+ setStatus('error');
180
+ throw err;
181
+ }
182
+ }, [cart, applyCart]);
183
+ const linesAdd = React.useCallback(async (lines) => {
184
+ const cartId = await ensureCartId();
185
+ await runMutation(null, async () => {
186
+ const data = await gql(M_LINES_ADD, {
187
+ cartId,
188
+ lines: lines.map((l) => ({ merchandiseId: l.merchandiseId, quantity: l.quantity ?? 1 })),
189
+ });
190
+ return data.cartLinesAdd;
191
+ });
192
+ }, [ensureCartId, runMutation, gql]);
193
+ const linesUpdate = React.useCallback(async (lines) => {
194
+ // remove 走 remove
195
+ const toRemove = lines.filter((l) => l.quantity <= 0).map((l) => l.id);
196
+ const toUpdate = lines.filter((l) => l.quantity > 0);
197
+ // optimistic: 改 quantity
198
+ const optimistic = cart != null
199
+ ? {
200
+ ...cart,
201
+ lines: {
202
+ ...cart.lines,
203
+ nodes: cart.lines.nodes
204
+ .map((ln) => {
205
+ const upd = toUpdate.find((l) => l.id === ln.id);
206
+ return upd ? { ...ln, quantity: upd.quantity } : ln;
207
+ })
208
+ .filter((ln) => !toRemove.includes(ln.id)),
209
+ },
210
+ }
211
+ : null;
212
+ const cartId = await ensureCartId();
213
+ await runMutation(optimistic, async () => {
214
+ if (toRemove.length > 0) {
215
+ await gql(M_LINES_REMOVE, { cartId, lineIds: toRemove });
216
+ }
217
+ if (toUpdate.length === 0) {
218
+ const data = await gql(Q_GET_CART, { id: cartId });
219
+ return { cart: data.cart, userErrors: [] };
220
+ }
221
+ const data = await gql(M_LINES_UPDATE, { cartId, lines: toUpdate });
222
+ return data.cartLinesUpdate;
223
+ });
224
+ }, [cart, ensureCartId, runMutation, gql]);
225
+ const linesRemove = React.useCallback(async (lineIds) => {
226
+ const optimistic = cart != null
227
+ ? {
228
+ ...cart,
229
+ lines: { ...cart.lines, nodes: cart.lines.nodes.filter((l) => !lineIds.includes(l.id)) },
230
+ }
231
+ : null;
232
+ const cartId = await ensureCartId();
233
+ await runMutation(optimistic, async () => {
234
+ const data = await gql(M_LINES_REMOVE, { cartId, lineIds });
235
+ return data.cartLinesRemove;
236
+ });
237
+ }, [cart, ensureCartId, runMutation, gql]);
238
+ const subscribe = React.useCallback((listener) => {
239
+ listenersRef.current.add(listener);
240
+ return () => {
241
+ listenersRef.current.delete(listener);
242
+ };
243
+ }, []);
244
+ const value = React.useMemo(() => ({ cart, status, error, linesAdd, linesUpdate, linesRemove, refetch, subscribe }), [cart, status, error, linesAdd, linesUpdate, linesRemove, refetch, subscribe]);
245
+ return _jsx(CartContext.Provider, { value: value, children: children });
246
+ }
247
+ /**
248
+ * 拿 cart context。Provider 外 throw。
249
+ */
250
+ export function useCart() {
251
+ const v = React.useContext(CartContext);
252
+ if (!v)
253
+ throw new Error('useCart must be used inside <CartProvider>');
254
+ return v;
255
+ }
256
+ /**
257
+ * 非 throw 版本:Provider 外返回 null。
258
+ * 给可选 fallback 用,比如 <AddToCartButton> 在没 Provider 时退化到 props.onAdd。
259
+ */
260
+ export function useCartOptional() {
261
+ return React.useContext(CartContext);
262
+ }
263
+ //# sourceMappingURL=CartProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CartProvider.js","sourceRoot":"","sources":["../../src/components/CartProvider.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * <Image> — 响应式商品图片
3
+ *
4
+ * 对齐 @shopify/hydrogen 的 <Image>:
5
+ * - 自动生成 srcset(5 个分辨率档:375 / 750 / 1024 / 1536 / 1920)
6
+ * - sizes 媒体查询
7
+ * - loading="lazy" 默认(首屏可关)
8
+ * - decoding="async"
9
+ * - aspectRatio 占位(不抖)
10
+ * - 自动 alt
11
+ *
12
+ * srcset 里的 url 通过 ?width= query 表达,服务端可忽略或接 cdn-cgi/image。
13
+ *
14
+ * 用法:
15
+ * <Image data={{ url, altText, width, height }} sizes="(min-width: 768px) 50vw, 100vw" />
16
+ * <Image data={...} aspectRatio="1/1" />
17
+ * <Image data={...} loading="eager" /> // 首屏
18
+ */
19
+ import * as React from 'react';
20
+ export interface ImageData {
21
+ url: string;
22
+ altText?: string | null;
23
+ width?: number | null;
24
+ height?: number | null;
25
+ }
26
+ export interface ImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'src' | 'srcSet' | 'loading'> {
27
+ /** 必传:图片对象 */
28
+ data: ImageData;
29
+ /** 媒体查询 sizes,例如 (min-width: 768px) 50vw, 100vw */
30
+ sizes?: string;
31
+ /** 强制宽高比,例如 '1/1' '4/3' '16/9' */
32
+ aspectRatio?: string;
33
+ /** lazy(默认) / eager(首屏图用) */
34
+ loading?: 'lazy' | 'eager';
35
+ /** 自定义 srcset 档位 */
36
+ widths?: number[];
37
+ }
38
+ export declare function Image(props: ImageProps): import("react/jsx-runtime").JSX.Element;
39
+ //# sourceMappingURL=Image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../../src/components/Image.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,UAAW,SAAQ,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC/G,cAAc;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAkBD,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,2CAmCtC"}
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // 默认 srcset 档位
3
+ const DEFAULT_WIDTHS = [375, 750, 1024, 1536, 1920];
4
+ /** 给 url 加 ?width=N query */
5
+ function withWidth(url, width) {
6
+ try {
7
+ const u = new URL(url);
8
+ u.searchParams.set('width', String(width));
9
+ return u.toString();
10
+ }
11
+ catch {
12
+ // 不是绝对 URL,按相对路径处理
13
+ const sep = url.includes('?') ? '&' : '?';
14
+ return `${url}${sep}width=${width}`;
15
+ }
16
+ }
17
+ export function Image(props) {
18
+ const { data, sizes, aspectRatio, loading = 'lazy', widths = DEFAULT_WIDTHS, style, alt: altProp, ...rest } = props;
19
+ const srcset = widths.map((w) => `${withWidth(data.url, w)} ${w}w`).join(', ');
20
+ // src 用中间档位
21
+ const src = withWidth(data.url, widths[Math.floor(widths.length / 2)]);
22
+ const finalStyle = {
23
+ ...(aspectRatio ? { aspectRatio, objectFit: 'cover' } : null),
24
+ ...style,
25
+ };
26
+ return (_jsx("img", { ...rest, src: src, srcSet: srcset, sizes: sizes, alt: altProp ?? data.altText ?? '', loading: loading, decoding: "async", width: data.width || undefined, height: data.height || undefined, style: finalStyle }));
27
+ }
28
+ //# sourceMappingURL=Image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Image.js","sourceRoot":"","sources":["../../src/components/Image.tsx"],"names":[],"mappings":";AAyCA,eAAe;AACf,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAEpD,6BAA6B;AAC7B,SAAS,SAAS,CAAC,GAAW,EAAE,KAAa;IAC3C,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,mBAAmB;QACnB,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QAC1C,OAAO,GAAG,GAAG,GAAG,GAAG,SAAS,KAAK,EAAE,CAAC;IACtC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,KAAiB;IACrC,MAAM,EACJ,IAAI,EACJ,KAAK,EACL,WAAW,EACX,OAAO,GAAG,MAAM,EAChB,MAAM,GAAG,cAAc,EACvB,KAAK,EACL,GAAG,EAAE,OAAO,EACZ,GAAG,IAAI,EACR,GAAG,KAAK,CAAC;IAEV,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/E,YAAY;IACZ,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvE,MAAM,UAAU,GAAwB;QACtC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,OAAgB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACtE,GAAG,KAAK;KACT,CAAC;IAEF,OAAO,CACL,iBACM,IAAI,EACR,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,OAAO,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAClC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAC,OAAO,EAChB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,SAAS,EAC9B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,SAAS,EAChC,KAAK,EAAE,UAAU,GACjB,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * <Money> — 货币格式化
3
+ *
4
+ * 对齐 @shopify/hydrogen 的 <Money>。
5
+ * - 自动按 currencyCode + locale 用 Intl.NumberFormat 格式化
6
+ * - 支持 withoutCurrency / withoutTrailingZeros
7
+ * - 支持 as 渲染成任意元素(默认 <span>)
8
+ * - 支持 measurement(unit price,例如 ¥9.99/100g)
9
+ *
10
+ * 用法:
11
+ * <Money data={{ amount: '9.99', currencyCode: 'CNY' }} />
12
+ * <Money data={{ ... }} withoutCurrency />
13
+ * <Money data={{ ... }} as="div" measurement={{ referenceUnit: 'kg', quantity: 0.1 }} />
14
+ */
15
+ import * as React from 'react';
16
+ export interface MoneyData {
17
+ amount: string;
18
+ currencyCode: string;
19
+ }
20
+ export interface MoneyMeasurement {
21
+ /** 比如 'kg' / '100g' / 'l' */
22
+ referenceUnit: string;
23
+ /** 比如 0.1 表示每 100g */
24
+ quantity?: number;
25
+ }
26
+ export interface MoneyProps extends React.HTMLAttributes<HTMLElement> {
27
+ /** 必传:金额对象 */
28
+ data: MoneyData;
29
+ /** 不显示货币符号,只显示数字 */
30
+ withoutCurrency?: boolean;
31
+ /** 整数时不显示 .00 */
32
+ withoutTrailingZeros?: boolean;
33
+ /** locale 字符串(zh-CN / en-US / ...)。默认按 currencyCode 推断 */
34
+ locale?: string;
35
+ /** 渲染元素,默认 span */
36
+ as?: keyof JSX.IntrinsicElements;
37
+ /** unit price:渲染 ¥99.00/100g */
38
+ measurement?: MoneyMeasurement;
39
+ }
40
+ export declare function Money(props: MoneyProps): import("react/jsx-runtime").JSX.Element;
41
+ //# sourceMappingURL=Money.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Money.d.ts","sourceRoot":"","sources":["../../src/components/Money.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAW,SAAQ,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC;IACnE,cAAc;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,oBAAoB;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB;IACjB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB;IACnB,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,iBAAiB,CAAC;IACjC,gCAAgC;IAChC,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC;AAYD,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,2CAwDtC"}
@@ -0,0 +1,53 @@
1
+ import { jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useShopOptional } from './ShopProvider';
3
+ // currencyCode → 默认 locale 推断
4
+ const DEFAULT_LOCALE_BY_CURRENCY = {
5
+ CNY: 'zh-CN',
6
+ USD: 'en-US',
7
+ EUR: 'de-DE',
8
+ GBP: 'en-GB',
9
+ JPY: 'ja-JP',
10
+ KRW: 'ko-KR',
11
+ };
12
+ export function Money(props) {
13
+ const { data, withoutCurrency, withoutTrailingZeros, locale: localeProp, as, measurement, ...rest } = props;
14
+ const Tag = as || 'span';
15
+ const shop = useShopOptional();
16
+ const locale = localeProp ||
17
+ shop?.locale ||
18
+ DEFAULT_LOCALE_BY_CURRENCY[data.currencyCode] ||
19
+ 'en-US';
20
+ const amount = Number(data.amount);
21
+ let formatted;
22
+ try {
23
+ const opts = withoutCurrency
24
+ ? {
25
+ minimumFractionDigits: withoutTrailingZeros && Number.isInteger(amount) ? 0 : 2,
26
+ maximumFractionDigits: 2,
27
+ }
28
+ : {
29
+ style: 'currency',
30
+ currency: data.currencyCode,
31
+ minimumFractionDigits: withoutTrailingZeros && Number.isInteger(amount) ? 0 : 2,
32
+ maximumFractionDigits: 2,
33
+ };
34
+ formatted = new Intl.NumberFormat(locale, opts).format(amount);
35
+ }
36
+ catch {
37
+ // Fallback:Intl 不支持的 currencyCode
38
+ formatted = withoutCurrency ? amount.toFixed(2) : `${data.currencyCode} ${amount.toFixed(2)}`;
39
+ }
40
+ // measurement suffix(unit price)
41
+ let suffix = '';
42
+ if (measurement) {
43
+ const q = measurement.quantity ?? 1;
44
+ if (q === 1) {
45
+ suffix = `/${measurement.referenceUnit}`;
46
+ }
47
+ else {
48
+ suffix = `/${q}${measurement.referenceUnit}`;
49
+ }
50
+ }
51
+ return (_jsxs(Tag, { ...rest, "data-money": data.amount, "data-money-currency": data.currencyCode, children: [formatted, suffix] }));
52
+ }
53
+ //# sourceMappingURL=Money.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Money.js","sourceRoot":"","sources":["../../src/components/Money.tsx"],"names":[],"mappings":";AAgBA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AA6BjD,8BAA8B;AAC9B,MAAM,0BAA0B,GAA2B;IACzD,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;CACb,CAAC;AAEF,MAAM,UAAU,KAAK,CAAC,KAAiB;IACrC,MAAM,EACJ,IAAI,EACJ,eAAe,EACf,oBAAoB,EACpB,MAAM,EAAE,UAAU,EAClB,EAAE,EACF,WAAW,EACX,GAAG,IAAI,EACR,GAAG,KAAK,CAAC;IAEV,MAAM,GAAG,GAAQ,EAAE,IAAI,MAAM,CAAC;IAC9B,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;IAC/B,MAAM,MAAM,GACV,UAAU;QACV,IAAI,EAAE,MAAM;QACZ,0BAA0B,CAAC,IAAI,CAAC,YAAY,CAAC;QAC7C,OAAO,CAAC;IACV,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEnC,IAAI,SAAiB,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,IAAI,GAA6B,eAAe;YACpD,CAAC,CAAC;gBACE,qBAAqB,EAAE,oBAAoB,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC/E,qBAAqB,EAAE,CAAC;aACzB;YACH,CAAC,CAAC;gBACE,KAAK,EAAE,UAAU;gBACjB,QAAQ,EAAE,IAAI,CAAC,YAAY;gBAC3B,qBAAqB,EAAE,oBAAoB,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC/E,qBAAqB,EAAE,CAAC;aACzB,CAAC;QACN,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,kCAAkC;QAClC,SAAS,GAAG,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAChG,CAAC;IAED,iCAAiC;IACjC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,CAAC,GAAG,WAAW,CAAC,QAAQ,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACZ,MAAM,GAAG,IAAI,WAAW,CAAC,aAAa,EAAE,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,IAAI,CAAC,GAAG,WAAW,CAAC,aAAa,EAAE,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,OAAO,CACL,MAAC,GAAG,OAAK,IAAI,gBAAc,IAAI,CAAC,MAAM,yBAAuB,IAAI,CAAC,YAAY,aAC3E,SAAS,EACT,MAAM,IACH,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * <ProductOptionsProvider> + useProductOptions()
3
+ *
4
+ * 给商品详情页用。包住 product 后,自动管理 selectedVariantId 状态 +
5
+ * 暴露当前 variant、available options、setOption 等。
6
+ *
7
+ * 用法:
8
+ * <ProductOptionsProvider product={product}>
9
+ * <ProductDetailBody />
10
+ * </ProductOptionsProvider>
11
+ *
12
+ * function ProductDetailBody() {
13
+ * const { selectedVariant, options, setOptionValue } = useProductOptions();
14
+ * return ...;
15
+ * }
16
+ */
17
+ import * as React from 'react';
18
+ interface SelectedOption {
19
+ name: string;
20
+ value: string;
21
+ }
22
+ interface ProductVariantLike {
23
+ id: string;
24
+ availableForSale: boolean;
25
+ selectedOptions?: SelectedOption[];
26
+ price?: {
27
+ amount: string;
28
+ currencyCode: string;
29
+ };
30
+ compareAtPrice?: {
31
+ amount: string;
32
+ currencyCode: string;
33
+ } | null;
34
+ }
35
+ interface ProductLike {
36
+ id: string;
37
+ options?: Array<{
38
+ name: string;
39
+ values: string[];
40
+ }>;
41
+ variants: {
42
+ nodes: ProductVariantLike[];
43
+ };
44
+ }
45
+ export interface ProductOptionItem {
46
+ name: string;
47
+ values: Array<{
48
+ value: string;
49
+ available: boolean;
50
+ isSelected: boolean;
51
+ }>;
52
+ }
53
+ export interface ProductOptionsContextValue {
54
+ product: ProductLike;
55
+ selectedVariant: ProductVariantLike | null;
56
+ selectedOptions: Record<string, string>;
57
+ options: ProductOptionItem[];
58
+ setOptionValue: (name: string, value: string) => void;
59
+ setVariantById: (variantId: string) => void;
60
+ }
61
+ export interface ProductOptionsProviderProps {
62
+ product: ProductLike;
63
+ /** 初始 selectedVariantId;默认第一个可购 variant */
64
+ initialVariantId?: string;
65
+ children: React.ReactNode;
66
+ }
67
+ export declare function ProductOptionsProvider(props: ProductOptionsProviderProps): import("react/jsx-runtime").JSX.Element;
68
+ export declare function useProductOptions(): ProductOptionsContextValue;
69
+ export {};
70
+ //# sourceMappingURL=ProductOptionsProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductOptionsProvider.d.ts","sourceRoot":"","sources":["../../src/components/ProductOptionsProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,UAAU,cAAc;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;CAAE;AAEzD,UAAU,kBAAkB;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,cAAc,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAClE;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACpD,QAAQ,EAAE;QAAE,KAAK,EAAE,kBAAkB,EAAE,CAAA;KAAE,CAAC;CAC3C;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC3E;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,WAAW,CAAC;IACrB,eAAe,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC3C,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAID,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,WAAW,CAAC;IACrB,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,2BAA2B,2CAoFxE;AAED,wBAAgB,iBAAiB,IAAI,0BAA0B,CAI9D"}
@@ -0,0 +1,93 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * <ProductOptionsProvider> + useProductOptions()
4
+ *
5
+ * 给商品详情页用。包住 product 后,自动管理 selectedVariantId 状态 +
6
+ * 暴露当前 variant、available options、setOption 等。
7
+ *
8
+ * 用法:
9
+ * <ProductOptionsProvider product={product}>
10
+ * <ProductDetailBody />
11
+ * </ProductOptionsProvider>
12
+ *
13
+ * function ProductDetailBody() {
14
+ * const { selectedVariant, options, setOptionValue } = useProductOptions();
15
+ * return ...;
16
+ * }
17
+ */
18
+ import * as React from 'react';
19
+ const Ctx = React.createContext(null);
20
+ export function ProductOptionsProvider(props) {
21
+ const { product, initialVariantId, children } = props;
22
+ const variants = product.variants.nodes;
23
+ const findDefault = React.useCallback(() => {
24
+ if (initialVariantId) {
25
+ const found = variants.find((v) => v.id === initialVariantId);
26
+ if (found)
27
+ return found;
28
+ }
29
+ return variants.find((v) => v.availableForSale) ?? variants[0] ?? null;
30
+ }, [initialVariantId, variants]);
31
+ const [selectedVariantId, setSelectedVariantId] = React.useState(findDefault()?.id ?? null);
32
+ React.useEffect(() => {
33
+ setSelectedVariantId(findDefault()?.id ?? null);
34
+ }, [findDefault]);
35
+ const selectedVariant = variants.find((v) => v.id === selectedVariantId) ?? null;
36
+ const selectedOptions = React.useMemo(() => {
37
+ const m = {};
38
+ for (const so of selectedVariant?.selectedOptions ?? [])
39
+ m[so.name] = so.value;
40
+ return m;
41
+ }, [selectedVariant]);
42
+ const options = React.useMemo(() => {
43
+ const dimMap = new Map();
44
+ // 收集所有 option 维度
45
+ if (product.options?.length) {
46
+ for (const opt of product.options) {
47
+ dimMap.set(opt.name, new Set(opt.values));
48
+ }
49
+ }
50
+ else {
51
+ for (const v of variants) {
52
+ for (const so of v.selectedOptions ?? []) {
53
+ if (!dimMap.has(so.name))
54
+ dimMap.set(so.name, new Set());
55
+ dimMap.get(so.name).add(so.value);
56
+ }
57
+ }
58
+ }
59
+ return Array.from(dimMap.entries()).map(([name, vals]) => ({
60
+ name,
61
+ values: Array.from(vals).map((v) => ({
62
+ value: v,
63
+ available: variants.some((variant) => variant.availableForSale &&
64
+ variant.selectedOptions?.some((so) => so.name === name && so.value === v)),
65
+ isSelected: selectedOptions[name] === v,
66
+ })),
67
+ }));
68
+ }, [product, variants, selectedOptions]);
69
+ const setOptionValue = React.useCallback((name, value) => {
70
+ const next = { ...selectedOptions, [name]: value };
71
+ // 找匹配 variant
72
+ const target = variants.find((v) => Object.entries(next).every(([n, val]) => v.selectedOptions?.some((so) => so.name === n && so.value === val)));
73
+ if (target)
74
+ setSelectedVariantId(target.id);
75
+ }, [selectedOptions, variants]);
76
+ const setVariantById = React.useCallback((id) => setSelectedVariantId(id), []);
77
+ const value = {
78
+ product,
79
+ selectedVariant,
80
+ selectedOptions,
81
+ options,
82
+ setOptionValue,
83
+ setVariantById,
84
+ };
85
+ return _jsx(Ctx.Provider, { value: value, children: children });
86
+ }
87
+ export function useProductOptions() {
88
+ const v = React.useContext(Ctx);
89
+ if (!v)
90
+ throw new Error('useProductOptions must be used inside <ProductOptionsProvider>');
91
+ return v;
92
+ }
93
+ //# sourceMappingURL=ProductOptionsProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductOptionsProvider.js","sourceRoot":"","sources":["../../src/components/ProductOptionsProvider.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAgC/B,MAAM,GAAG,GAAG,KAAK,CAAC,aAAa,CAAoC,IAAI,CAAC,CAAC;AASzE,MAAM,UAAU,sBAAsB,CAAC,KAAkC;IACvE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IAExC,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QACzC,IAAI,gBAAgB,EAAE,CAAC;YACrB,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,gBAAgB,CAAC,CAAC;YAC9D,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1B,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IACzE,CAAC,EAAE,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEjC,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,KAAK,CAAC,QAAQ,CAC9D,WAAW,EAAE,EAAE,EAAE,IAAI,IAAI,CAC1B,CAAC;IAEF,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,oBAAoB,CAAC,WAAW,EAAE,EAAE,EAAE,IAAI,IAAI,CAAC,CAAC;IAClD,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,MAAM,eAAe,GACnB,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,iBAAiB,CAAC,IAAI,IAAI,CAAC;IAE3D,MAAM,eAAe,GAA2B,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QACjE,MAAM,CAAC,GAA2B,EAAE,CAAC;QACrC,KAAK,MAAM,EAAE,IAAI,eAAe,EAAE,eAAe,IAAI,EAAE;YAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC;QAC/E,OAAO,CAAC,CAAC;IACX,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IAEtB,MAAM,OAAO,GAAwB,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QACtD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;QAC9C,iBAAiB;QACjB,IAAI,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;YAC5B,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBAClC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;aAAM,CAAC;YACN,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACzB,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,eAAe,IAAI,EAAE,EAAE,CAAC;oBACzC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;wBAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;oBACzD,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAE,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;gBACrC,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YACzD,IAAI;YACJ,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACnC,KAAK,EAAE,CAAC;gBACR,SAAS,EAAE,QAAQ,CAAC,IAAI,CACtB,CAAC,OAAO,EAAE,EAAE,CACV,OAAO,CAAC,gBAAgB;oBACxB,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC,CAC5E;gBACD,UAAU,EAAE,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC;aACxC,CAAC,CAAC;SACJ,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC;IAEzC,MAAM,cAAc,GAAG,KAAK,CAAC,WAAW,CACtC,CAAC,IAAY,EAAE,KAAa,EAAE,EAAE;QAC9B,MAAM,IAAI,GAAG,EAAE,GAAG,eAAe,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC;QACnD,cAAc;QACd,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CACjC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CACtC,CAAC,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,KAAK,GAAG,CAAC,CACnE,CACF,CAAC;QACF,IAAI,MAAM;YAAE,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC,EACD,CAAC,eAAe,EAAE,QAAQ,CAAC,CAC5B,CAAC;IAEF,MAAM,cAAc,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,oBAAoB,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAEvF,MAAM,KAAK,GAA+B;QACxC,OAAO;QACP,eAAe;QACf,eAAe;QACf,OAAO;QACP,cAAc;QACd,cAAc;KACf,CAAC;IAEF,OAAO,KAAC,GAAG,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAgB,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,MAAM,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;IAC1F,OAAO,CAAC,CAAC;AACX,CAAC"}