@shopbb/helium 0.5.10 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/cache/withCache.d.ts +49 -0
  2. package/dist/cache/withCache.d.ts.map +1 -0
  3. package/dist/cache/withCache.js +117 -0
  4. package/dist/cache/withCache.js.map +1 -0
  5. package/dist/components/AddToCartButton.d.ts +28 -22
  6. package/dist/components/AddToCartButton.d.ts.map +1 -1
  7. package/dist/components/AddToCartButton.js +36 -47
  8. package/dist/components/AddToCartButton.js.map +1 -1
  9. package/dist/components/BuyNowButton.d.ts +45 -0
  10. package/dist/components/BuyNowButton.d.ts.map +1 -0
  11. package/dist/components/BuyNowButton.js +49 -0
  12. package/dist/components/BuyNowButton.js.map +1 -0
  13. package/dist/components/CartCheckoutButton.d.ts +39 -0
  14. package/dist/components/CartCheckoutButton.d.ts.map +1 -0
  15. package/dist/components/CartCheckoutButton.js +32 -0
  16. package/dist/components/CartCheckoutButton.js.map +1 -0
  17. package/dist/components/CartCost.d.ts +43 -0
  18. package/dist/components/CartCost.d.ts.map +1 -0
  19. package/dist/components/CartCost.js +34 -0
  20. package/dist/components/CartCost.js.map +1 -0
  21. package/dist/components/CartForm.d.ts +201 -0
  22. package/dist/components/CartForm.d.ts.map +1 -0
  23. package/dist/components/CartForm.js +213 -0
  24. package/dist/components/CartForm.js.map +1 -0
  25. package/dist/components/CartLineProvider.d.ts +78 -0
  26. package/dist/components/CartLineProvider.d.ts.map +1 -0
  27. package/dist/components/CartLineProvider.js +46 -0
  28. package/dist/components/CartLineProvider.js.map +1 -0
  29. package/dist/components/CartLineQuantity.d.ts +24 -0
  30. package/dist/components/CartLineQuantity.d.ts.map +1 -0
  31. package/dist/components/CartLineQuantity.js +9 -0
  32. package/dist/components/CartLineQuantity.js.map +1 -0
  33. package/dist/components/DiscountSelector.d.ts.map +1 -1
  34. package/dist/components/DiscountSelector.js +8 -19
  35. package/dist/components/DiscountSelector.js.map +1 -1
  36. package/dist/components/Image.d.ts +18 -0
  37. package/dist/components/Image.d.ts.map +1 -1
  38. package/dist/components/Image.js +26 -0
  39. package/dist/components/Image.js.map +1 -1
  40. package/dist/components/Pagination.d.ts +82 -0
  41. package/dist/components/Pagination.d.ts.map +1 -0
  42. package/dist/components/Pagination.js +84 -0
  43. package/dist/components/Pagination.js.map +1 -0
  44. package/dist/components/RichText.d.ts +78 -0
  45. package/dist/components/RichText.d.ts.map +1 -0
  46. package/dist/components/RichText.js +93 -0
  47. package/dist/components/RichText.js.map +1 -0
  48. package/dist/components/Seo.d.ts +25 -0
  49. package/dist/components/Seo.d.ts.map +1 -0
  50. package/dist/components/Seo.js +54 -0
  51. package/dist/components/Seo.js.map +1 -0
  52. package/dist/components/hooks/useMoney.d.ts +40 -0
  53. package/dist/components/hooks/useMoney.d.ts.map +1 -0
  54. package/dist/components/hooks/useMoney.js +60 -0
  55. package/dist/components/hooks/useMoney.js.map +1 -0
  56. package/dist/components/hooks/useOptimisticCart.d.ts +50 -0
  57. package/dist/components/hooks/useOptimisticCart.d.ts.map +1 -0
  58. package/dist/components/hooks/useOptimisticCart.js +138 -0
  59. package/dist/components/hooks/useOptimisticCart.js.map +1 -0
  60. package/dist/components/index.d.ts +28 -0
  61. package/dist/components/index.d.ts.map +1 -1
  62. package/dist/components/index.js +21 -0
  63. package/dist/components/index.js.map +1 -1
  64. package/dist/createCartHandler.d.ts.map +1 -1
  65. package/dist/createCartHandler.js +57 -0
  66. package/dist/createCartHandler.js.map +1 -1
  67. package/dist/csp/csp.d.ts +57 -0
  68. package/dist/csp/csp.d.ts.map +1 -0
  69. package/dist/csp/csp.js +73 -0
  70. package/dist/csp/csp.js.map +1 -0
  71. package/dist/customer/createCustomerAccountClient.d.ts +43 -0
  72. package/dist/customer/createCustomerAccountClient.d.ts.map +1 -0
  73. package/dist/customer/createCustomerAccountClient.js +68 -0
  74. package/dist/customer/createCustomerAccountClient.js.map +1 -0
  75. package/dist/handleCartFormAction.d.ts +39 -0
  76. package/dist/handleCartFormAction.d.ts.map +1 -0
  77. package/dist/handleCartFormAction.js +103 -0
  78. package/dist/handleCartFormAction.js.map +1 -0
  79. package/dist/index.d.ts +18 -0
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +11 -0
  82. package/dist/index.js.map +1 -1
  83. package/dist/routing/storefrontRedirect.d.ts +37 -0
  84. package/dist/routing/storefrontRedirect.d.ts.map +1 -0
  85. package/dist/routing/storefrontRedirect.js +64 -0
  86. package/dist/routing/storefrontRedirect.js.map +1 -0
  87. package/dist/seo/getSeoMeta.d.ts +68 -0
  88. package/dist/seo/getSeoMeta.d.ts.map +1 -0
  89. package/dist/seo/getSeoMeta.js +89 -0
  90. package/dist/seo/getSeoMeta.js.map +1 -0
  91. package/dist/sitemap/sitemap.d.ts +55 -0
  92. package/dist/sitemap/sitemap.d.ts.map +1 -0
  93. package/dist/sitemap/sitemap.js +93 -0
  94. package/dist/sitemap/sitemap.js.map +1 -0
  95. package/dist/types.d.ts +12 -0
  96. package/dist/types.d.ts.map +1 -1
  97. package/dist/utils/flattenConnection.d.ts +25 -0
  98. package/dist/utils/flattenConnection.d.ts.map +1 -0
  99. package/dist/utils/flattenConnection.js +25 -0
  100. package/dist/utils/flattenConnection.js.map +1 -0
  101. package/dist/utils/parseGid.d.ts +17 -0
  102. package/dist/utils/parseGid.d.ts.map +1 -0
  103. package/dist/utils/parseGid.js +19 -0
  104. package/dist/utils/parseGid.js.map +1 -0
  105. package/package.json +1 -1
  106. package/src/cache/withCache.ts +144 -0
  107. package/src/components/AddToCartButton.tsx +94 -56
  108. package/src/components/BuyNowButton.tsx +135 -0
  109. package/src/components/CartCheckoutButton.tsx +97 -0
  110. package/src/components/CartCost.tsx +65 -0
  111. package/src/components/CartForm.tsx +311 -0
  112. package/src/components/CartLineProvider.tsx +77 -0
  113. package/src/components/CartLineQuantity.tsx +37 -0
  114. package/src/components/DiscountSelector.tsx +34 -45
  115. package/src/components/Image.tsx +27 -0
  116. package/src/components/Pagination.tsx +139 -0
  117. package/src/components/RichText.tsx +122 -0
  118. package/src/components/Seo.tsx +61 -0
  119. package/src/components/hooks/useMoney.ts +87 -0
  120. package/src/components/hooks/useOptimisticCart.ts +173 -0
  121. package/src/components/index.ts +44 -0
  122. package/src/createCartHandler.ts +71 -0
  123. package/src/csp/csp.tsx +119 -0
  124. package/src/customer/createCustomerAccountClient.ts +89 -0
  125. package/src/handleCartFormAction.ts +129 -0
  126. package/src/index.ts +24 -0
  127. package/src/routing/storefrontRedirect.ts +86 -0
  128. package/src/seo/getSeoMeta.ts +125 -0
  129. package/src/sitemap/sitemap.ts +121 -0
  130. package/src/types.ts +12 -1
  131. package/src/utils/flattenConnection.ts +33 -0
  132. package/src/utils/parseGid.ts +25 -0
@@ -0,0 +1,49 @@
1
+ /**
2
+ * createWithCache + InMemoryCache — 对齐 Hydrogen
3
+ *
4
+ * Hydrogen 的 `createWithCache({cache, waitUntil})` 返回一个高阶函数,
5
+ * 用 `withCache.run(strategy, cacheKey, fn)` 包裹任意异步函数,自动加 cache。
6
+ *
7
+ * 在 Cloudflare Workers 上 cache 可以是:
8
+ * - `caches.default` / `caches.open(...)` (Cache API — 真正的 edge cache)
9
+ * - `InMemoryCache` (本 worker 实例内存,per-instance;轻量缓存)
10
+ *
11
+ * 用法:
12
+ *
13
+ * const withCache = createWithCache({ cache: await caches.open('helium'), waitUntil });
14
+ *
15
+ * const products = await withCache.run(
16
+ * CacheLong(),
17
+ * ['products', 'page', '1'],
18
+ * async () => storefront.query(PRODUCTS_QUERY, { variables }),
19
+ * );
20
+ *
21
+ * 简化点(vs Hydrogen):
22
+ * - 不实现 stale-while-revalidate 后台 revalidate(Workers 内复杂,先做基础)
23
+ * - 不实现 stale-if-error
24
+ * - 提供 InMemoryCache 兜底(test / 没 caches.open 的环境)
25
+ */
26
+ import type { CacheStrategy } from '../types';
27
+ export type CachingStrategy = CacheStrategy;
28
+ export type CacheKey = string | (string | number)[];
29
+ export interface WithCacheOptions {
30
+ cache: Cache | InMemoryCache;
31
+ waitUntil?: (p: Promise<any>) => void;
32
+ }
33
+ export interface WithCache {
34
+ /** 用 cache 包裹异步函数。命中 cache 直接返回;未命中执行 fn 写入 */
35
+ run<T>(strategy: CachingStrategy, key: CacheKey, fn: () => Promise<T>): Promise<T>;
36
+ /** 直接读 cache(不执行 fn) */
37
+ get<T>(key: CacheKey): Promise<T | null>;
38
+ /** 写 cache */
39
+ set<T>(strategy: CachingStrategy, key: CacheKey, value: T): Promise<void>;
40
+ }
41
+ export declare function createWithCache(opts: WithCacheOptions): WithCache;
42
+ export declare class InMemoryCache {
43
+ private store;
44
+ get<T = any>(key: string): T | null;
45
+ set<T = any>(key: string, value: T, maxAgeSeconds: number): void;
46
+ delete(key: string): boolean;
47
+ clear(): void;
48
+ }
49
+ //# sourceMappingURL=withCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withCache.d.ts","sourceRoot":"","sources":["../../src/cache/withCache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,MAAM,MAAM,eAAe,GAAG,aAAa,CAAC;AAE5C,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;AAEpD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,KAAK,GAAG,aAAa,CAAC;IAC7B,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,SAAS;IACxB,+CAA+C;IAC/C,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACnF,wBAAwB;IACxB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACzC,cAAc;IACd,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3E;AAgBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,CA8CjE;AAWD,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAA+B;IAE5C,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI;IAUnC,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI;IAKhE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI5B,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * createWithCache + InMemoryCache — 对齐 Hydrogen
3
+ *
4
+ * Hydrogen 的 `createWithCache({cache, waitUntil})` 返回一个高阶函数,
5
+ * 用 `withCache.run(strategy, cacheKey, fn)` 包裹任意异步函数,自动加 cache。
6
+ *
7
+ * 在 Cloudflare Workers 上 cache 可以是:
8
+ * - `caches.default` / `caches.open(...)` (Cache API — 真正的 edge cache)
9
+ * - `InMemoryCache` (本 worker 实例内存,per-instance;轻量缓存)
10
+ *
11
+ * 用法:
12
+ *
13
+ * const withCache = createWithCache({ cache: await caches.open('helium'), waitUntil });
14
+ *
15
+ * const products = await withCache.run(
16
+ * CacheLong(),
17
+ * ['products', 'page', '1'],
18
+ * async () => storefront.query(PRODUCTS_QUERY, { variables }),
19
+ * );
20
+ *
21
+ * 简化点(vs Hydrogen):
22
+ * - 不实现 stale-while-revalidate 后台 revalidate(Workers 内复杂,先做基础)
23
+ * - 不实现 stale-if-error
24
+ * - 提供 InMemoryCache 兜底(test / 没 caches.open 的环境)
25
+ */
26
+ function toCacheKey(key) {
27
+ if (typeof key === 'string')
28
+ return key;
29
+ return key.map(String).join(':');
30
+ }
31
+ function buildCacheControl(strategy) {
32
+ const dirs = [];
33
+ if (strategy.mode === 'NONE')
34
+ return 'no-store';
35
+ if (strategy.mode === 'PUBLIC')
36
+ dirs.push('public');
37
+ if (strategy.maxAge != null)
38
+ dirs.push(`max-age=${strategy.maxAge}`);
39
+ if (strategy.staleWhileRevalidate != null)
40
+ dirs.push(`stale-while-revalidate=${strategy.staleWhileRevalidate}`);
41
+ return dirs.join(', ');
42
+ }
43
+ export function createWithCache(opts) {
44
+ const { cache, waitUntil } = opts;
45
+ async function get(key) {
46
+ const cacheKey = toCacheKey(key);
47
+ if (cache instanceof InMemoryCache) {
48
+ return cache.get(cacheKey);
49
+ }
50
+ const req = new Request(`https://helium.cache/${encodeURIComponent(cacheKey)}`);
51
+ const cached = await cache.match(req);
52
+ if (!cached)
53
+ return null;
54
+ try {
55
+ return (await cached.json());
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ async function set(strategy, key, value) {
62
+ if (strategy.mode === 'NONE')
63
+ return;
64
+ const cacheKey = toCacheKey(key);
65
+ if (cache instanceof InMemoryCache) {
66
+ cache.set(cacheKey, value, strategy.maxAge ?? 0);
67
+ return;
68
+ }
69
+ const req = new Request(`https://helium.cache/${encodeURIComponent(cacheKey)}`);
70
+ const res = new Response(JSON.stringify(value), {
71
+ headers: {
72
+ 'Content-Type': 'application/json',
73
+ 'Cache-Control': buildCacheControl(strategy),
74
+ },
75
+ });
76
+ const p = cache.put(req, res);
77
+ if (waitUntil)
78
+ waitUntil(p);
79
+ else
80
+ await p;
81
+ }
82
+ async function run(strategy, key, fn) {
83
+ if (strategy.mode === 'NONE')
84
+ return fn();
85
+ const cached = await get(key);
86
+ if (cached !== null)
87
+ return cached;
88
+ const value = await fn();
89
+ await set(strategy, key, value);
90
+ return value;
91
+ }
92
+ return { run, get, set };
93
+ }
94
+ export class InMemoryCache {
95
+ store = new Map();
96
+ get(key) {
97
+ const e = this.store.get(key);
98
+ if (!e)
99
+ return null;
100
+ if (e.expiresAt > 0 && Date.now() > e.expiresAt) {
101
+ this.store.delete(key);
102
+ return null;
103
+ }
104
+ return e.value;
105
+ }
106
+ set(key, value, maxAgeSeconds) {
107
+ const expiresAt = maxAgeSeconds > 0 ? Date.now() + maxAgeSeconds * 1000 : 0;
108
+ this.store.set(key, { value, expiresAt });
109
+ }
110
+ delete(key) {
111
+ return this.store.delete(key);
112
+ }
113
+ clear() {
114
+ this.store.clear();
115
+ }
116
+ }
117
+ //# sourceMappingURL=withCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withCache.js","sourceRoot":"","sources":["../../src/cache/withCache.ts"],"names":[],"mappings":"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"}
@@ -1,41 +1,47 @@
1
1
  /**
2
2
  * <AddToCartButton> — 加入购物车按钮
3
3
  *
4
- * 对齐 @shopify/hydrogen <AddToCartButton>:
5
- * - 内置 loading 态、disabled、error 处理
6
- * - 支持 onAdd 回调(实际加购逻辑由外部 cart hook 提供)
7
- * - 触发 analytics 事件钩子
8
- * - 不带样式,商家自己控制
4
+ * 对齐 Shopify Hydrogen:本质是包了一个 <CartForm action={LinesAdd}> + <button type="submit">。
9
5
  *
10
- * 用法(Phase 1,需要传 onAdd):
11
- * const { addLine } = useCart();
12
- * <AddToCartButton
13
- * variantId={selectedVariant.id}
14
- * quantity={1}
15
- * disabled={!selectedVariant.availableForSale}
16
- * onAdd={(vid, qty) => addLine(vid, qty)}
17
- * >
18
- * 加入购物车
19
- * </AddToCartButton>
6
+ * - SSR HTML 是原生 <form>,无 JS 也能加购(form 提交 → 服务端 redirect 回 referrer)
7
+ * - hydrate JS 拦截 submit → fetch /cart → CartProvider.applyCart → 无刷新
8
+ * - loading 状态由 CartForm.fetcher 暴露,通过 useFetcher() 读取
20
9
  *
21
- * Phase 2 后:组件会自动从 <CartProvider> 拿,无需 onAdd 也能用。
10
+ * 商家也可以直接组合 <CartForm>,不用我们这个 wrapper:
11
+ *
12
+ * <CartForm route="/cart" action={CartForm.ACTIONS.LinesAdd} inputs={{ lines: [...] }}>
13
+ * <button type="submit">加入购物车</button>
14
+ * </CartForm>
22
15
  */
23
16
  import * as React from 'react';
24
- export interface AddToCartButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick' | 'onError'> {
17
+ export interface AddToCartButtonProps {
25
18
  /** 必传:variant 的 GID(gid://shopbb/ProductVariant/...) */
26
19
  variantId: string;
27
20
  /** 数量,默认 1 */
28
21
  quantity?: number;
29
- /** 实际加购回调。Phase 1 必传;Phase 2 后可从 Provider 自动获取 */
30
- onAdd?: (variantId: string, quantity: number) => Promise<void>;
31
- /** 加购成功回调(如导航到 /cart) */
22
+ /** 行级 attributes(如 gift_message) */
23
+ attributes?: Array<{
24
+ key: string;
25
+ value: string;
26
+ }>;
27
+ /** 加购成功回调(仅 hydrated 后触发) */
32
28
  onAdded?: () => void;
33
29
  /** 加购失败回调 */
34
30
  onError?: (err: Error) => void;
35
- /** 加购中显示的文本,默认 "加入中..." */
31
+ /** 加购中显示的文本 */
36
32
  loadingText?: React.ReactNode;
37
- /** 不可用时显示的文本,默认 "缺货" */
33
+ /** 不可用时显示的文本 */
38
34
  unavailableText?: React.ReactNode;
35
+ /** disabled */
36
+ disabled?: boolean;
37
+ /** 包装 form className */
38
+ className?: string;
39
+ /** 按钮 className */
40
+ buttonClassName?: string;
41
+ /** 提交的 route,默认 /cart */
42
+ route?: string;
43
+ /** 按钮内容 */
44
+ children?: React.ReactNode;
39
45
  }
40
46
  export declare function AddToCartButton(props: AddToCartButtonProps): import("react/jsx-runtime").JSX.Element;
41
47
  //# sourceMappingURL=AddToCartButton.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AddToCartButton.d.ts","sourceRoot":"","sources":["../../src/components/AddToCartButton.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACtH,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa;IACb,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B,2BAA2B;IAC3B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,wBAAwB;IACxB,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACnC;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAwD1D"}
1
+ {"version":3,"file":"AddToCartButton.d.ts","sourceRoot":"","sources":["../../src/components/AddToCartButton.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,oBAAoB;IACnC,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa;IACb,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B,eAAe;IACf,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,gBAAgB;IAChB,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAClC,eAAe;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW;IACX,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAwC1D"}
@@ -2,62 +2,51 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  /**
3
3
  * <AddToCartButton> — 加入购物车按钮
4
4
  *
5
- * 对齐 @shopify/hydrogen <AddToCartButton>:
6
- * - 内置 loading 态、disabled、error 处理
7
- * - 支持 onAdd 回调(实际加购逻辑由外部 cart hook 提供)
8
- * - 触发 analytics 事件钩子
9
- * - 不带样式,商家自己控制
5
+ * 对齐 Shopify Hydrogen:本质是包了一个 <CartForm action={LinesAdd}> + <button type="submit">。
10
6
  *
11
- * 用法(Phase 1,需要传 onAdd):
12
- * const { addLine } = useCart();
13
- * <AddToCartButton
14
- * variantId={selectedVariant.id}
15
- * quantity={1}
16
- * disabled={!selectedVariant.availableForSale}
17
- * onAdd={(vid, qty) => addLine(vid, qty)}
18
- * >
19
- * 加入购物车
20
- * </AddToCartButton>
7
+ * - SSR HTML 是原生 <form>,无 JS 也能加购(form 提交 → 服务端 redirect 回 referrer)
8
+ * - hydrate JS 拦截 submit → fetch /cart → CartProvider.applyCart → 无刷新
9
+ * - loading 状态由 CartForm.fetcher 暴露,通过 useFetcher() 读取
21
10
  *
22
- * Phase 2 后:组件会自动从 <CartProvider> 拿,无需 onAdd 也能用。
11
+ * 商家也可以直接组合 <CartForm>,不用我们这个 wrapper:
12
+ *
13
+ * <CartForm route="/cart" action={CartForm.ACTIONS.LinesAdd} inputs={{ lines: [...] }}>
14
+ * <button type="submit">加入购物车</button>
15
+ * </CartForm>
23
16
  */
24
17
  import * as React from 'react';
25
- import { useCartOptional } from './CartProvider';
18
+ import { CartForm, useFetcher } from './CartForm';
26
19
  import { useAnalytics } from './AnalyticsProvider';
27
20
  export function AddToCartButton(props) {
28
- const { variantId, quantity = 1, onAdd, onAdded, onError, loadingText = '加入中...', unavailableText = '缺货', children = '加入购物车', disabled: disabledProp, ...rest } = props;
29
- const [adding, setAdding] = React.useState(false);
30
- const cart = useCartOptional();
21
+ const { variantId, quantity = 1, attributes, onAdded, onError, loadingText = '加入中...', unavailableText = '缺货', disabled = false, className, buttonClassName, route = '/cart', children = '加入购物车', } = props;
22
+ const line = { merchandiseId: variantId, quantity };
23
+ if (attributes && attributes.length > 0)
24
+ line.attributes = attributes;
25
+ return (_jsx(CartForm, { route: route, action: CartForm.ACTIONS.LinesAdd, inputs: { lines: [line] }, className: className, children: _jsx(AddToCartButtonInner, { disabled: disabled, buttonClassName: buttonClassName, loadingText: loadingText, unavailableText: unavailableText, variantId: variantId, quantity: quantity, onAdded: onAdded, onError: onError, children: children }) }));
26
+ }
27
+ /**
28
+ * Inner component — 合法地 useFetcher / useEffect,监听 fetcher.state 变化。
29
+ */
30
+ function AddToCartButtonInner({ disabled, buttonClassName, loadingText, unavailableText, children, variantId, quantity, onAdded, onError, }) {
31
+ const fetcher = useFetcher();
31
32
  const analytics = useAnalytics();
32
- const handleClick = async (e) => {
33
- e.preventDefault();
34
- if (adding || disabledProp)
35
- return;
36
- setAdding(true);
37
- try {
38
- if (onAdd) {
39
- // 商家显式传了 onAdd 优先用(Phase 1 兼容路径)
40
- await onAdd(variantId, quantity);
33
+ // 监听 fetcher 完成
34
+ const prevState = React.useRef(fetcher.state);
35
+ React.useEffect(() => {
36
+ const wasNonIdle = prevState.current !== 'idle';
37
+ const isIdle = fetcher.state === 'idle';
38
+ if (wasNonIdle && isIdle) {
39
+ if (fetcher.error) {
40
+ onError?.(new Error(fetcher.error));
41
41
  }
42
- else if (cart) {
43
- // 自动从 <CartProvider>
44
- await cart.linesAdd([{ merchandiseId: variantId, quantity }]);
42
+ else if (fetcher.data?.cart) {
43
+ analytics.emit('add_to_cart', { variantId, quantity });
44
+ onAdded?.();
45
45
  }
46
- else {
47
- console.warn('[AddToCartButton] no onAdd and no <CartProvider> — button does nothing');
48
- return;
49
- }
50
- analytics.emit('add_to_cart', { variantId, quantity });
51
- onAdded?.();
52
- }
53
- catch (err) {
54
- console.error('[AddToCartButton] add failed:', err);
55
- onError?.(err instanceof Error ? err : new Error(String(err)));
56
- }
57
- finally {
58
- setAdding(false);
59
46
  }
60
- };
61
- return (_jsx("button", { type: "button", ...rest, "data-add-to-cart": true, "data-loading": adding ? '' : undefined, disabled: disabledProp || adding, onClick: handleClick, children: adding ? loadingText : disabledProp ? unavailableText : children }));
47
+ prevState.current = fetcher.state;
48
+ }, [fetcher.state, fetcher.error, fetcher.data, onAdded, onError, analytics, variantId, quantity]);
49
+ const adding = fetcher.state !== 'idle';
50
+ return (_jsx("button", { type: "submit", className: buttonClassName, disabled: disabled || adding, "data-add-to-cart": true, "data-loading": adding ? '' : undefined, children: adding ? loadingText : disabled ? unavailableText : children }));
62
51
  }
63
52
  //# sourceMappingURL=AddToCartButton.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AddToCartButton.js","sourceRoot":"","sources":["../../src/components/AddToCartButton.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAmBnD,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,MAAM,EACJ,SAAS,EACT,QAAQ,GAAG,CAAC,EACZ,KAAK,EACL,OAAO,EACP,OAAO,EACP,WAAW,GAAG,QAAQ,EACtB,eAAe,GAAG,IAAI,EACtB,QAAQ,GAAG,OAAO,EAClB,QAAQ,EAAE,YAAY,EACtB,GAAG,IAAI,EACR,GAAG,KAAK,CAAC;IAEV,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IAEjC,MAAM,WAAW,GAAG,KAAK,EAAE,CAAsC,EAAE,EAAE;QACnE,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,IAAI,MAAM,IAAI,YAAY;YAAE,OAAO;QAEnC,SAAS,CAAC,IAAI,CAAC,CAAC;QAChB,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,CAAC;gBACV,iCAAiC;gBACjC,MAAM,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YACnC,CAAC;iBAAM,IAAI,IAAI,EAAE,CAAC;gBAChB,uBAAuB;gBACvB,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;YAChE,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CAAC,wEAAwE,CAAC,CAAC;gBACvF,OAAO;YACT,CAAC;YACD,SAAS,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;YACvD,OAAO,EAAE,EAAE,CAAC;QACd,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC;YACpD,OAAO,EAAE,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjE,CAAC;gBAAS,CAAC;YACT,SAAS,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CACL,iBACE,IAAI,EAAC,QAAQ,KACT,IAAI,4CAEM,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,EACrC,QAAQ,EAAE,YAAY,IAAI,MAAM,EAChC,OAAO,EAAE,WAAW,YAEnB,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,GAC1D,CACV,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"AddToCartButton.js","sourceRoot":"","sources":["../../src/components/AddToCartButton.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * <BuyNowButton> — 对齐 Hydrogen React
3
+ *
4
+ * "立即购买" — 创建/添加 line + 跳 checkout,一步到位。
5
+ *
6
+ * 实现:
7
+ * 1. 内部包 <CartForm action="LinesAdd" inputs={{lines, navigateOnSuccess: '/checkout'}}>
8
+ * 2. submit 成功后 client 自动 navigate 到 checkoutUrl(或 fallback /checkout)
9
+ * 3. 无 JS 时:浏览器走 form POST,服务端 303 跳 /checkout
10
+ *
11
+ * 用法:
12
+ * <BuyNowButton variantId="gid://..." quantity={1}>立即购买</BuyNowButton>
13
+ */
14
+ import * as React from 'react';
15
+ export interface BuyNowButtonProps {
16
+ /** 必传 variant GID */
17
+ variantId: string;
18
+ /** 数量,默认 1 */
19
+ quantity?: number;
20
+ /** 行级 attributes */
21
+ attributes?: Array<{
22
+ key: string;
23
+ value: string;
24
+ }>;
25
+ /** 加购后跳转的 URL(client 端),默认走 cart.checkoutUrl,回退 /checkout */
26
+ redirectTo?: string;
27
+ /** 失败回调 */
28
+ onError?: (err: Error) => void;
29
+ /** Loading 文案 */
30
+ loadingText?: React.ReactNode;
31
+ /** Disabled 文案 */
32
+ unavailableText?: React.ReactNode;
33
+ /** disabled */
34
+ disabled?: boolean;
35
+ /** Form className */
36
+ className?: string;
37
+ /** 按钮 className */
38
+ buttonClassName?: string;
39
+ /** 按钮 content */
40
+ children?: React.ReactNode;
41
+ /** 提交 route,默认 /cart */
42
+ route?: string;
43
+ }
44
+ export declare function BuyNowButton(props: BuyNowButtonProps): import("react/jsx-runtime").JSX.Element;
45
+ //# sourceMappingURL=BuyNowButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BuyNowButton.d.ts","sourceRoot":"","sources":["../../src/components/BuyNowButton.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,iBAAiB;IAChC,qBAAqB;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB;IACpB,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW;IACX,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B,iBAAiB;IACjB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,kBAAkB;IAClB,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAClC,eAAe;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB;IACjB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,2CAwCpD"}
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * <BuyNowButton> — 对齐 Hydrogen React
4
+ *
5
+ * "立即购买" — 创建/添加 line + 跳 checkout,一步到位。
6
+ *
7
+ * 实现:
8
+ * 1. 内部包 <CartForm action="LinesAdd" inputs={{lines, navigateOnSuccess: '/checkout'}}>
9
+ * 2. submit 成功后 client 自动 navigate 到 checkoutUrl(或 fallback /checkout)
10
+ * 3. 无 JS 时:浏览器走 form POST,服务端 303 跳 /checkout
11
+ *
12
+ * 用法:
13
+ * <BuyNowButton variantId="gid://..." quantity={1}>立即购买</BuyNowButton>
14
+ */
15
+ import * as React from 'react';
16
+ import { CartForm, useFetcher } from './CartForm';
17
+ import { useAnalytics } from './AnalyticsProvider';
18
+ export function BuyNowButton(props) {
19
+ const { variantId, quantity = 1, attributes, redirectTo, onError, loadingText = '处理中...', unavailableText = '暂不可购', disabled = false, className, buttonClassName, children = '立即购买', route = '/cart', } = props;
20
+ const line = { merchandiseId: variantId, quantity };
21
+ if (attributes && attributes.length > 0)
22
+ line.attributes = attributes;
23
+ return (_jsx(CartForm, { route: route, action: CartForm.ACTIONS.LinesAdd, inputs: { lines: [line] }, className: className, children: _jsx(BuyNowButtonInner, { disabled: disabled, buttonClassName: buttonClassName, loadingText: loadingText, unavailableText: unavailableText, variantId: variantId, quantity: quantity, redirectTo: redirectTo, onError: onError, children: children }) }));
24
+ }
25
+ function BuyNowButtonInner({ disabled, buttonClassName, loadingText, unavailableText, children, variantId, quantity, redirectTo, onError, }) {
26
+ const fetcher = useFetcher();
27
+ const analytics = useAnalytics();
28
+ const prevState = React.useRef(fetcher.state);
29
+ React.useEffect(() => {
30
+ const wasNonIdle = prevState.current !== 'idle';
31
+ const isIdle = fetcher.state === 'idle';
32
+ if (wasNonIdle && isIdle) {
33
+ if (fetcher.error) {
34
+ onError?.(new Error(fetcher.error));
35
+ }
36
+ else if (fetcher.data?.cart) {
37
+ analytics.emit('buy_now', { variantId, quantity });
38
+ const target = redirectTo || fetcher.data.cart.checkoutUrl || '/checkout';
39
+ if (typeof window !== 'undefined') {
40
+ window.location.href = target;
41
+ }
42
+ }
43
+ }
44
+ prevState.current = fetcher.state;
45
+ }, [fetcher.state, fetcher.error, fetcher.data, redirectTo, onError, analytics, variantId, quantity]);
46
+ const pending = fetcher.state !== 'idle';
47
+ return (_jsx("button", { type: "submit", className: buttonClassName, disabled: disabled || pending, "data-buy-now": true, "data-loading": pending ? '' : undefined, children: pending ? loadingText : disabled ? unavailableText : children }));
48
+ }
49
+ //# sourceMappingURL=BuyNowButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BuyNowButton.js","sourceRoot":"","sources":["../../src/components/BuyNowButton.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * <CartCheckoutButton> — 对齐 Hydrogen React
3
+ *
4
+ * 渲染一个跳到 cart.checkoutUrl 的按钮。最常见的"去结算"入口。
5
+ *
6
+ * 行为:
7
+ * - 取 cart.checkoutUrl,无 cart / 空 cart 时按钮 disabled
8
+ * - 默认 <a> 元素(无 JS 也能用),可改成 <button> + onClick
9
+ * - 渲染纯样式,行为非业务逻辑
10
+ *
11
+ * 用法:
12
+ * <CartCheckoutButton>去结算</CartCheckoutButton>
13
+ *
14
+ * // 自定义渲染
15
+ * <CartCheckoutButton>
16
+ * {(href, disabled) => (
17
+ * <a href={href} className="my-cta" aria-disabled={disabled}>
18
+ * 结算 {cart.totalQuantity} 件
19
+ * </a>
20
+ * )}
21
+ * </CartCheckoutButton>
22
+ */
23
+ import * as React from 'react';
24
+ export interface CartCheckoutButtonProps {
25
+ /** className */
26
+ className?: string;
27
+ /** 强制 disabled(即使 cart 有 checkoutUrl) */
28
+ disabled?: boolean;
29
+ /** 按钮内容;不传默认 "去结算" */
30
+ children?: React.ReactNode | ((href: string, disabled: boolean) => React.ReactNode);
31
+ /** 自定义渲染元素,默认 a */
32
+ as?: 'a' | 'button';
33
+ /** 点击回调(事件触发) */
34
+ onClick?: React.MouseEventHandler;
35
+ /** 透传其它 props 到根元素 */
36
+ [key: string]: any;
37
+ }
38
+ export declare function CartCheckoutButton(props: CartCheckoutButtonProps): import("react/jsx-runtime").JSX.Element;
39
+ //# sourceMappingURL=CartCheckoutButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CartCheckoutButton.d.ts","sourceRoot":"","sources":["../../src/components/CartCheckoutButton.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,uBAAuB;IACtC,gBAAgB;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sBAAsB;IACtB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IACpF,mBAAmB;IACnB,EAAE,CAAC,EAAE,GAAG,GAAG,QAAQ,CAAC;IACpB,iBAAiB;IACjB,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC;IAClC,sBAAsB;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,2CAuDhE"}
@@ -0,0 +1,32 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCart } from './CartProvider';
3
+ export function CartCheckoutButton(props) {
4
+ const { className, disabled: disabledProp, children, as = 'a', onClick, ...rest } = props;
5
+ const { cart } = useCart();
6
+ const href = cart?.checkoutUrl;
7
+ const noCart = !cart || cart.totalQuantity === 0 || !href;
8
+ const disabled = !!disabledProp || noCart;
9
+ if (typeof children === 'function') {
10
+ return _jsx(_Fragment, { children: children(href || '#', disabled) });
11
+ }
12
+ const label = children ?? '去结算';
13
+ if (as === 'button') {
14
+ return (_jsx("button", { type: "button", className: className, disabled: disabled, "data-cart-checkout-button": true, onClick: (e) => {
15
+ if (disabled)
16
+ return;
17
+ onClick?.(e);
18
+ if (!e.defaultPrevented && href) {
19
+ window.location.href = href;
20
+ }
21
+ }, ...rest, children: label }));
22
+ }
23
+ // <a> 形式:无 JS 也能跳
24
+ return (_jsx("a", { href: disabled ? undefined : href, className: className, "aria-disabled": disabled || undefined, "data-cart-checkout-button": true, "data-disabled": disabled ? '' : undefined, onClick: (e) => {
25
+ if (disabled) {
26
+ e.preventDefault();
27
+ return;
28
+ }
29
+ onClick?.(e);
30
+ }, ...rest, children: label }));
31
+ }
32
+ //# sourceMappingURL=CartCheckoutButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CartCheckoutButton.js","sourceRoot":"","sources":["../../src/components/CartCheckoutButton.tsx"],"names":[],"mappings":";AAwBA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAiBzC,MAAM,UAAU,kBAAkB,CAAC,KAA8B;IAC/D,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;IAC1F,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC;IAE3B,MAAM,IAAI,GAAI,IAAY,EAAE,WAAiC,CAAC;IAC9D,MAAM,MAAM,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,aAAa,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;IAC1D,MAAM,QAAQ,GAAG,CAAC,CAAC,YAAY,IAAI,MAAM,CAAC;IAE1C,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;QACnC,OAAO,4BAAG,QAAQ,CAAC,IAAI,IAAI,GAAG,EAAE,QAAQ,CAAC,GAAI,CAAC;IAChD,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,IAAI,KAAK,CAAC;IAEhC,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;QACpB,OAAO,CACL,iBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,QAAQ,qCAElB,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;gBACb,IAAI,QAAQ;oBAAE,OAAO;gBACrB,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;gBACb,IAAI,CAAC,CAAC,CAAC,gBAAgB,IAAI,IAAI,EAAE,CAAC;oBAChC,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC;gBAC9B,CAAC;YACH,CAAC,KACG,IAAI,YAEP,KAAK,GACC,CACV,CAAC;IACJ,CAAC;IAED,kBAAkB;IAClB,OAAO,CACL,YACE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EACjC,SAAS,EAAE,SAAS,mBACL,QAAQ,IAAI,SAAS,sDAErB,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,EACxC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;YACb,IAAI,QAAQ,EAAE,CAAC;gBACb,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,OAAO;YACT,CAAC;YACD,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QACf,CAAC,KACG,IAAI,YAEP,KAAK,GACJ,CACL,CAAC;AACJ,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * <CartCost> — 对齐 Hydrogen React
3
+ *
4
+ * 从 <CartProvider> 拿 cart.cost 渲染金额。amountType 控制渲染哪个:
5
+ * "total" → cost.totalAmount (默认)
6
+ * "subtotal" → cost.subtotalAmount
7
+ * "tax" → cost.totalTaxAmount
8
+ * "duty" → cost.totalDutyAmount
9
+ * "discount" → cost.totalDiscountAmount(shopbb-extension)
10
+ *
11
+ * 内部用 <Money> 渲染,自动按 currencyCode 加货币符号。
12
+ *
13
+ * 用法:
14
+ * <CartCost amountType="subtotal" /> ¥300.00
15
+ * <CartCost amountType="discount" as="strong" /> ¥30.00
16
+ * <CartCost amountType="total" withoutTrailingZeros />
17
+ *
18
+ * 自定义渲染:
19
+ * <CartCost amountType="total">
20
+ * {(money) => <strong>{money.amount} {money.currencyCode}</strong>}
21
+ * </CartCost>
22
+ */
23
+ import * as React from 'react';
24
+ import { type MoneyProps } from './Money';
25
+ export interface MoneyValue {
26
+ amount: string;
27
+ currencyCode: string;
28
+ }
29
+ export interface CartCostProps extends Omit<MoneyProps, 'data' | 'children'> {
30
+ /**
31
+ * 渲染哪一项 cost。默认 "total"。
32
+ * - "total" : 含运费 / 税 / 折扣的最终金额
33
+ * - "subtotal" : 商品小计(折扣前 / 运费前)
34
+ * - "tax" : 税
35
+ * - "duty" : 关税
36
+ * - "discount" : 优惠金额(shopbb extension)
37
+ */
38
+ amountType?: 'total' | 'subtotal' | 'tax' | 'duty' | 'discount';
39
+ /** 自定义渲染:(money) => ReactNode */
40
+ children?: (money: MoneyValue) => React.ReactNode;
41
+ }
42
+ export declare function CartCost(props: CartCostProps): import("react/jsx-runtime").JSX.Element | null;
43
+ //# sourceMappingURL=CartCost.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CartCost.d.ts","sourceRoot":"","sources":["../../src/components/CartCost.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAEjD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAAC;IAC1E;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,UAAU,CAAC;IAChE,iCAAiC;IACjC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,KAAK,CAAC,SAAS,CAAC;CACnD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,kDAkB5C"}