@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.
- package/dist/cache/withCache.d.ts +49 -0
- package/dist/cache/withCache.d.ts.map +1 -0
- package/dist/cache/withCache.js +117 -0
- package/dist/cache/withCache.js.map +1 -0
- package/dist/components/AddToCartButton.d.ts +28 -22
- package/dist/components/AddToCartButton.d.ts.map +1 -1
- package/dist/components/AddToCartButton.js +36 -47
- package/dist/components/AddToCartButton.js.map +1 -1
- package/dist/components/BuyNowButton.d.ts +45 -0
- package/dist/components/BuyNowButton.d.ts.map +1 -0
- package/dist/components/BuyNowButton.js +49 -0
- package/dist/components/BuyNowButton.js.map +1 -0
- package/dist/components/CartCheckoutButton.d.ts +39 -0
- package/dist/components/CartCheckoutButton.d.ts.map +1 -0
- package/dist/components/CartCheckoutButton.js +32 -0
- package/dist/components/CartCheckoutButton.js.map +1 -0
- package/dist/components/CartCost.d.ts +43 -0
- package/dist/components/CartCost.d.ts.map +1 -0
- package/dist/components/CartCost.js +34 -0
- package/dist/components/CartCost.js.map +1 -0
- package/dist/components/CartForm.d.ts +201 -0
- package/dist/components/CartForm.d.ts.map +1 -0
- package/dist/components/CartForm.js +213 -0
- package/dist/components/CartForm.js.map +1 -0
- package/dist/components/CartLineProvider.d.ts +78 -0
- package/dist/components/CartLineProvider.d.ts.map +1 -0
- package/dist/components/CartLineProvider.js +46 -0
- package/dist/components/CartLineProvider.js.map +1 -0
- package/dist/components/CartLineQuantity.d.ts +24 -0
- package/dist/components/CartLineQuantity.d.ts.map +1 -0
- package/dist/components/CartLineQuantity.js +9 -0
- package/dist/components/CartLineQuantity.js.map +1 -0
- package/dist/components/DiscountSelector.d.ts.map +1 -1
- package/dist/components/DiscountSelector.js +8 -19
- package/dist/components/DiscountSelector.js.map +1 -1
- package/dist/components/Image.d.ts +18 -0
- package/dist/components/Image.d.ts.map +1 -1
- package/dist/components/Image.js +26 -0
- package/dist/components/Image.js.map +1 -1
- package/dist/components/Pagination.d.ts +82 -0
- package/dist/components/Pagination.d.ts.map +1 -0
- package/dist/components/Pagination.js +84 -0
- package/dist/components/Pagination.js.map +1 -0
- package/dist/components/RichText.d.ts +78 -0
- package/dist/components/RichText.d.ts.map +1 -0
- package/dist/components/RichText.js +93 -0
- package/dist/components/RichText.js.map +1 -0
- package/dist/components/Seo.d.ts +25 -0
- package/dist/components/Seo.d.ts.map +1 -0
- package/dist/components/Seo.js +54 -0
- package/dist/components/Seo.js.map +1 -0
- package/dist/components/hooks/useMoney.d.ts +40 -0
- package/dist/components/hooks/useMoney.d.ts.map +1 -0
- package/dist/components/hooks/useMoney.js +60 -0
- package/dist/components/hooks/useMoney.js.map +1 -0
- package/dist/components/hooks/useOptimisticCart.d.ts +50 -0
- package/dist/components/hooks/useOptimisticCart.d.ts.map +1 -0
- package/dist/components/hooks/useOptimisticCart.js +138 -0
- package/dist/components/hooks/useOptimisticCart.js.map +1 -0
- package/dist/components/index.d.ts +28 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +21 -0
- package/dist/components/index.js.map +1 -1
- package/dist/createCartHandler.d.ts.map +1 -1
- package/dist/createCartHandler.js +57 -0
- package/dist/createCartHandler.js.map +1 -1
- package/dist/csp/csp.d.ts +57 -0
- package/dist/csp/csp.d.ts.map +1 -0
- package/dist/csp/csp.js +73 -0
- package/dist/csp/csp.js.map +1 -0
- package/dist/customer/createCustomerAccountClient.d.ts +43 -0
- package/dist/customer/createCustomerAccountClient.d.ts.map +1 -0
- package/dist/customer/createCustomerAccountClient.js +68 -0
- package/dist/customer/createCustomerAccountClient.js.map +1 -0
- package/dist/handleCartFormAction.d.ts +39 -0
- package/dist/handleCartFormAction.d.ts.map +1 -0
- package/dist/handleCartFormAction.js +103 -0
- package/dist/handleCartFormAction.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/routing/storefrontRedirect.d.ts +37 -0
- package/dist/routing/storefrontRedirect.d.ts.map +1 -0
- package/dist/routing/storefrontRedirect.js +64 -0
- package/dist/routing/storefrontRedirect.js.map +1 -0
- package/dist/seo/getSeoMeta.d.ts +68 -0
- package/dist/seo/getSeoMeta.d.ts.map +1 -0
- package/dist/seo/getSeoMeta.js +89 -0
- package/dist/seo/getSeoMeta.js.map +1 -0
- package/dist/sitemap/sitemap.d.ts +55 -0
- package/dist/sitemap/sitemap.d.ts.map +1 -0
- package/dist/sitemap/sitemap.js +93 -0
- package/dist/sitemap/sitemap.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/flattenConnection.d.ts +25 -0
- package/dist/utils/flattenConnection.d.ts.map +1 -0
- package/dist/utils/flattenConnection.js +25 -0
- package/dist/utils/flattenConnection.js.map +1 -0
- package/dist/utils/parseGid.d.ts +17 -0
- package/dist/utils/parseGid.d.ts.map +1 -0
- package/dist/utils/parseGid.js +19 -0
- package/dist/utils/parseGid.js.map +1 -0
- package/package.json +1 -1
- package/src/cache/withCache.ts +144 -0
- package/src/components/AddToCartButton.tsx +94 -56
- package/src/components/BuyNowButton.tsx +135 -0
- package/src/components/CartCheckoutButton.tsx +97 -0
- package/src/components/CartCost.tsx +65 -0
- package/src/components/CartForm.tsx +311 -0
- package/src/components/CartLineProvider.tsx +77 -0
- package/src/components/CartLineQuantity.tsx +37 -0
- package/src/components/DiscountSelector.tsx +34 -45
- package/src/components/Image.tsx +27 -0
- package/src/components/Pagination.tsx +139 -0
- package/src/components/RichText.tsx +122 -0
- package/src/components/Seo.tsx +61 -0
- package/src/components/hooks/useMoney.ts +87 -0
- package/src/components/hooks/useOptimisticCart.ts +173 -0
- package/src/components/index.ts +44 -0
- package/src/createCartHandler.ts +71 -0
- package/src/csp/csp.tsx +119 -0
- package/src/customer/createCustomerAccountClient.ts +89 -0
- package/src/handleCartFormAction.ts +129 -0
- package/src/index.ts +24 -0
- package/src/routing/storefrontRedirect.ts +86 -0
- package/src/seo/getSeoMeta.ts +125 -0
- package/src/sitemap/sitemap.ts +121 -0
- package/src/types.ts +12 -1
- package/src/utils/flattenConnection.ts +33 -0
- package/src/utils/parseGid.ts +25 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCart } from './CartProvider';
|
|
3
|
+
import { Money } from './Money';
|
|
4
|
+
export function CartCost(props) {
|
|
5
|
+
const { amountType = 'total', children, ...moneyProps } = props;
|
|
6
|
+
const { cart } = useCart();
|
|
7
|
+
if (!cart?.cost)
|
|
8
|
+
return null;
|
|
9
|
+
const cost = cart.cost;
|
|
10
|
+
let money = null;
|
|
11
|
+
switch (amountType) {
|
|
12
|
+
case 'total':
|
|
13
|
+
money = cost.totalAmount ?? null;
|
|
14
|
+
break;
|
|
15
|
+
case 'subtotal':
|
|
16
|
+
money = cost.subtotalAmount ?? null;
|
|
17
|
+
break;
|
|
18
|
+
case 'tax':
|
|
19
|
+
money = cost.totalTaxAmount ?? cost.taxAmount ?? null;
|
|
20
|
+
break;
|
|
21
|
+
case 'duty':
|
|
22
|
+
money = cost.totalDutyAmount ?? cost.dutyAmount ?? null;
|
|
23
|
+
break;
|
|
24
|
+
case 'discount':
|
|
25
|
+
money = cost.totalDiscountAmount ?? null;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
if (!money)
|
|
29
|
+
return null;
|
|
30
|
+
if (children)
|
|
31
|
+
return _jsx(_Fragment, { children: children(money) });
|
|
32
|
+
return _jsx(Money, { data: money, ...moneyProps });
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=CartCost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartCost.js","sourceRoot":"","sources":["../../src/components/CartCost.tsx"],"names":[],"mappings":";AAwBA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,KAAK,EAAmB,MAAM,SAAS,CAAC;AAqBjD,MAAM,UAAU,QAAQ,CAAC,KAAoB;IAC3C,MAAM,EAAE,UAAU,GAAG,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,GAAG,KAAK,CAAC;IAChE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC;IAC3B,IAAI,CAAC,IAAI,EAAE,IAAI;QAAE,OAAO,IAAI,CAAC;IAE7B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAW,CAAC;IAC9B,IAAI,KAAK,GAAsB,IAAI,CAAC;IACpC,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,OAAO;YAAE,KAAK,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC;YAAC,MAAM;QACtD,KAAK,UAAU;YAAE,KAAK,GAAG,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC;YAAC,MAAM;QAC5D,KAAK,KAAK;YAAE,KAAK,GAAG,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;YAAC,MAAM;QACzE,KAAK,MAAM;YAAE,KAAK,GAAG,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;YAAC,MAAM;QAC5E,KAAK,UAAU;YAAE,KAAK,GAAG,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC;YAAC,MAAM;IACnE,CAAC;IACD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,IAAI,QAAQ;QAAE,OAAO,4BAAG,QAAQ,CAAC,KAAK,CAAC,GAAI,CAAC;IAC5C,OAAO,KAAC,KAAK,IAAC,IAAI,EAAE,KAAK,KAAM,UAAU,GAAI,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <CartForm> — 对齐 Shopify Hydrogen 的 CartForm 组件
|
|
3
|
+
*
|
|
4
|
+
* Hydrogen 文档:https://shopify.dev/docs/api/hydrogen/latest/components/cartform
|
|
5
|
+
*
|
|
6
|
+
* 用法:
|
|
7
|
+
*
|
|
8
|
+
* import { CartForm } from '@shopby/helium';
|
|
9
|
+
*
|
|
10
|
+
* // 加入购物车
|
|
11
|
+
* <CartForm
|
|
12
|
+
* action={CartForm.ACTIONS.LinesAdd}
|
|
13
|
+
* inputs={{ lines: [{ merchandiseId, quantity: 1 }] }}
|
|
14
|
+
* route="/cart"
|
|
15
|
+
* >
|
|
16
|
+
* <button type="submit">加入购物车</button>
|
|
17
|
+
* </CartForm>
|
|
18
|
+
*
|
|
19
|
+
* // 自定义按钮渲染
|
|
20
|
+
* <CartForm action={CartForm.ACTIONS.LinesAdd} inputs={{ lines }}>
|
|
21
|
+
* {(fetcher) => (
|
|
22
|
+
* <button disabled={fetcher.state !== 'idle'}>
|
|
23
|
+
* {fetcher.state === 'submitting' ? '添加中…' : '加入购物车'}
|
|
24
|
+
* </button>
|
|
25
|
+
* )}
|
|
26
|
+
* </CartForm>
|
|
27
|
+
*
|
|
28
|
+
* 服务端(route action):
|
|
29
|
+
*
|
|
30
|
+
* import { CartForm } from '@shopbb/helium';
|
|
31
|
+
*
|
|
32
|
+
* export async function action(request, context) {
|
|
33
|
+
* const formData = await request.formData();
|
|
34
|
+
* const { action, inputs } = CartForm.getFormInput(formData);
|
|
35
|
+
* // ... switch(action) { ... }
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* ─────────────────────────────────────────────────────────────────
|
|
39
|
+
*
|
|
40
|
+
* 实现说明:
|
|
41
|
+
*
|
|
42
|
+
* - 渲染原生 <form method="POST">,无 JS 也能工作(form 默认提交 = 整页 POST)
|
|
43
|
+
* - INPUT_NAME = "cartFormInput"(hidden input,value = JSON.stringify({action, inputs}))
|
|
44
|
+
* - JS 加载后,由 helium 提供的 useCartFormFetcher hook(或 useFetcher 实现) 拦截 submit 实现"不跳页"
|
|
45
|
+
* - 当前简化实现:hydrate 后 onSubmit preventDefault,自己用 fetch 调,调完后通过 CartProvider.applyCart 更新
|
|
46
|
+
*/
|
|
47
|
+
import * as React from 'react';
|
|
48
|
+
export declare const CART_FORM_ACTIONS: {
|
|
49
|
+
/** Update cart attributes */
|
|
50
|
+
readonly AttributesUpdateInput: "AttributesUpdateInput";
|
|
51
|
+
/** Update buyer identity (email, customerAccessToken, country, etc.) */
|
|
52
|
+
readonly BuyerIdentityUpdate: "BuyerIdentityUpdate";
|
|
53
|
+
/** Create a new cart */
|
|
54
|
+
readonly Create: "Create";
|
|
55
|
+
/** Replace discount codes */
|
|
56
|
+
readonly DiscountCodesUpdate: "DiscountCodesUpdate";
|
|
57
|
+
/** Replace gift card codes */
|
|
58
|
+
readonly GiftCardCodesUpdate: "GiftCardCodesUpdate";
|
|
59
|
+
/** Append gift card codes */
|
|
60
|
+
readonly GiftCardCodesAdd: "GiftCardCodesAdd";
|
|
61
|
+
/** Remove specific gift card codes */
|
|
62
|
+
readonly GiftCardCodesRemove: "GiftCardCodesRemove";
|
|
63
|
+
/** Add cart lines */
|
|
64
|
+
readonly LinesAdd: "LinesAdd";
|
|
65
|
+
/** Update cart lines (quantity / attributes) */
|
|
66
|
+
readonly LinesUpdate: "LinesUpdate";
|
|
67
|
+
/** Remove cart lines */
|
|
68
|
+
readonly LinesRemove: "LinesRemove";
|
|
69
|
+
/** Update cart note */
|
|
70
|
+
readonly NoteUpdate: "NoteUpdate";
|
|
71
|
+
/** Update selected delivery options */
|
|
72
|
+
readonly SelectedDeliveryOptionsUpdate: "SelectedDeliveryOptionsUpdate";
|
|
73
|
+
/** Set metafields */
|
|
74
|
+
readonly MetafieldsSet: "MetafieldsSet";
|
|
75
|
+
/** Delete metafields */
|
|
76
|
+
readonly MetafieldsDelete: "MetafieldsDelete";
|
|
77
|
+
};
|
|
78
|
+
export type CartFormAction = (typeof CART_FORM_ACTIONS)[keyof typeof CART_FORM_ACTIONS] | `Custom${string}`;
|
|
79
|
+
export type CartLineInput = {
|
|
80
|
+
merchandiseId: string;
|
|
81
|
+
quantity?: number;
|
|
82
|
+
attributes?: Array<{
|
|
83
|
+
key: string;
|
|
84
|
+
value: string;
|
|
85
|
+
}>;
|
|
86
|
+
};
|
|
87
|
+
export type CartLineUpdateInput = {
|
|
88
|
+
id: string;
|
|
89
|
+
quantity?: number;
|
|
90
|
+
merchandiseId?: string;
|
|
91
|
+
attributes?: Array<{
|
|
92
|
+
key: string;
|
|
93
|
+
value: string;
|
|
94
|
+
}>;
|
|
95
|
+
};
|
|
96
|
+
interface OtherFormData {
|
|
97
|
+
[key: string]: unknown;
|
|
98
|
+
}
|
|
99
|
+
export type CartFormInput = {
|
|
100
|
+
action: 'LinesAdd';
|
|
101
|
+
inputs: {
|
|
102
|
+
lines: CartLineInput[];
|
|
103
|
+
} & OtherFormData;
|
|
104
|
+
} | {
|
|
105
|
+
action: 'LinesUpdate';
|
|
106
|
+
inputs: {
|
|
107
|
+
lines: CartLineUpdateInput[];
|
|
108
|
+
} & OtherFormData;
|
|
109
|
+
} | {
|
|
110
|
+
action: 'LinesRemove';
|
|
111
|
+
inputs: {
|
|
112
|
+
lineIds: string[];
|
|
113
|
+
} & OtherFormData;
|
|
114
|
+
} | {
|
|
115
|
+
action: 'DiscountCodesUpdate';
|
|
116
|
+
inputs: {
|
|
117
|
+
discountCodes: string[];
|
|
118
|
+
} & OtherFormData;
|
|
119
|
+
} | {
|
|
120
|
+
action: 'NoteUpdate';
|
|
121
|
+
inputs: {
|
|
122
|
+
note: string;
|
|
123
|
+
} & OtherFormData;
|
|
124
|
+
} | {
|
|
125
|
+
action: 'BuyerIdentityUpdate';
|
|
126
|
+
inputs: {
|
|
127
|
+
buyerIdentity: Record<string, unknown>;
|
|
128
|
+
} & OtherFormData;
|
|
129
|
+
} | {
|
|
130
|
+
action: 'AttributesUpdateInput';
|
|
131
|
+
inputs: {
|
|
132
|
+
attributes: Array<{
|
|
133
|
+
key: string;
|
|
134
|
+
value: string;
|
|
135
|
+
}>;
|
|
136
|
+
} & OtherFormData;
|
|
137
|
+
} | {
|
|
138
|
+
action: 'Create';
|
|
139
|
+
inputs: {
|
|
140
|
+
input: Record<string, unknown>;
|
|
141
|
+
} & OtherFormData;
|
|
142
|
+
} | {
|
|
143
|
+
action: `Custom${string}`;
|
|
144
|
+
inputs: Record<string, unknown>;
|
|
145
|
+
};
|
|
146
|
+
/** 服务端 / 客户端共享的 hidden field name — 对齐 Hydrogen */
|
|
147
|
+
declare const INPUT_NAME = "cartFormInput";
|
|
148
|
+
/**
|
|
149
|
+
* Fetcher 状态对象 — 模仿 Remix useFetcher
|
|
150
|
+
*
|
|
151
|
+
* 关键属性:
|
|
152
|
+
* - state: 'idle' | 'submitting' | 'loading'
|
|
153
|
+
* - data: 服务端 action 返回的 JSON(成功时)
|
|
154
|
+
* - error: 错误信息
|
|
155
|
+
*/
|
|
156
|
+
export interface CartFormFetcher {
|
|
157
|
+
state: 'idle' | 'submitting' | 'loading';
|
|
158
|
+
data: any;
|
|
159
|
+
error: string | null;
|
|
160
|
+
/** 取最近一次提交的 form data(optimistic UI 用) */
|
|
161
|
+
formData: FormData | null;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 在 <CartForm> 内部任何深度的组件里拿 fetcher 状态。
|
|
165
|
+
*
|
|
166
|
+
* <CartForm action="LinesAdd" inputs={{lines}}>
|
|
167
|
+
* <MyButton />
|
|
168
|
+
* </CartForm>
|
|
169
|
+
*
|
|
170
|
+
* function MyButton() {
|
|
171
|
+
* const fetcher = useFetcher();
|
|
172
|
+
* return <button disabled={fetcher.state !== 'idle'}>添加</button>;
|
|
173
|
+
* }
|
|
174
|
+
*/
|
|
175
|
+
export declare function useFetcher(): CartFormFetcher;
|
|
176
|
+
export interface CartFormCommonProps {
|
|
177
|
+
/** children 可以是普通节点,也可以是 (fetcher) => ReactNode */
|
|
178
|
+
children: React.ReactNode | ((fetcher: CartFormFetcher) => React.ReactNode);
|
|
179
|
+
/** 提交的 route。默认 "/cart" */
|
|
180
|
+
route?: string;
|
|
181
|
+
/** Optional key — 同一 key 的 fetcher state 共享。当前简化版未使用 */
|
|
182
|
+
fetcherKey?: string;
|
|
183
|
+
/** 表单 className(不影响行为) */
|
|
184
|
+
className?: string;
|
|
185
|
+
}
|
|
186
|
+
export type CartFormProps = CartFormInput & CartFormCommonProps;
|
|
187
|
+
interface CartFormImpl {
|
|
188
|
+
(props: CartFormProps): React.ReactElement;
|
|
189
|
+
ACTIONS: typeof CART_FORM_ACTIONS;
|
|
190
|
+
INPUT_NAME: typeof INPUT_NAME;
|
|
191
|
+
/**
|
|
192
|
+
* 服务端 helper:从 request.formData() 解析出 { action, inputs }
|
|
193
|
+
*
|
|
194
|
+
* const formData = await request.formData();
|
|
195
|
+
* const { action, inputs } = CartForm.getFormInput(formData);
|
|
196
|
+
*/
|
|
197
|
+
getFormInput: (formData: FormData) => CartFormInput;
|
|
198
|
+
}
|
|
199
|
+
declare const CartForm: CartFormImpl;
|
|
200
|
+
export { CartForm };
|
|
201
|
+
//# sourceMappingURL=CartForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartForm.d.ts","sourceRoot":"","sources":["../../src/components/CartForm.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* <CartForm> — 对齐 Shopify Hydrogen 的 CartForm 组件
|
|
4
|
+
*
|
|
5
|
+
* Hydrogen 文档:https://shopify.dev/docs/api/hydrogen/latest/components/cartform
|
|
6
|
+
*
|
|
7
|
+
* 用法:
|
|
8
|
+
*
|
|
9
|
+
* import { CartForm } from '@shopby/helium';
|
|
10
|
+
*
|
|
11
|
+
* // 加入购物车
|
|
12
|
+
* <CartForm
|
|
13
|
+
* action={CartForm.ACTIONS.LinesAdd}
|
|
14
|
+
* inputs={{ lines: [{ merchandiseId, quantity: 1 }] }}
|
|
15
|
+
* route="/cart"
|
|
16
|
+
* >
|
|
17
|
+
* <button type="submit">加入购物车</button>
|
|
18
|
+
* </CartForm>
|
|
19
|
+
*
|
|
20
|
+
* // 自定义按钮渲染
|
|
21
|
+
* <CartForm action={CartForm.ACTIONS.LinesAdd} inputs={{ lines }}>
|
|
22
|
+
* {(fetcher) => (
|
|
23
|
+
* <button disabled={fetcher.state !== 'idle'}>
|
|
24
|
+
* {fetcher.state === 'submitting' ? '添加中…' : '加入购物车'}
|
|
25
|
+
* </button>
|
|
26
|
+
* )}
|
|
27
|
+
* </CartForm>
|
|
28
|
+
*
|
|
29
|
+
* 服务端(route action):
|
|
30
|
+
*
|
|
31
|
+
* import { CartForm } from '@shopbb/helium';
|
|
32
|
+
*
|
|
33
|
+
* export async function action(request, context) {
|
|
34
|
+
* const formData = await request.formData();
|
|
35
|
+
* const { action, inputs } = CartForm.getFormInput(formData);
|
|
36
|
+
* // ... switch(action) { ... }
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* ─────────────────────────────────────────────────────────────────
|
|
40
|
+
*
|
|
41
|
+
* 实现说明:
|
|
42
|
+
*
|
|
43
|
+
* - 渲染原生 <form method="POST">,无 JS 也能工作(form 默认提交 = 整页 POST)
|
|
44
|
+
* - INPUT_NAME = "cartFormInput"(hidden input,value = JSON.stringify({action, inputs}))
|
|
45
|
+
* - JS 加载后,由 helium 提供的 useCartFormFetcher hook(或 useFetcher 实现) 拦截 submit 实现"不跳页"
|
|
46
|
+
* - 当前简化实现:hydrate 后 onSubmit preventDefault,自己用 fetch 调,调完后通过 CartProvider.applyCart 更新
|
|
47
|
+
*/
|
|
48
|
+
import * as React from 'react';
|
|
49
|
+
import { useCartOptional } from './CartProvider';
|
|
50
|
+
import { __registerPendingMutation } from './hooks/useOptimisticCart';
|
|
51
|
+
// ============================================================
|
|
52
|
+
// Action 枚举 — 对齐 Hydrogen CartForm.ACTIONS
|
|
53
|
+
// ============================================================
|
|
54
|
+
export const CART_FORM_ACTIONS = {
|
|
55
|
+
/** Update cart attributes */
|
|
56
|
+
AttributesUpdateInput: 'AttributesUpdateInput',
|
|
57
|
+
/** Update buyer identity (email, customerAccessToken, country, etc.) */
|
|
58
|
+
BuyerIdentityUpdate: 'BuyerIdentityUpdate',
|
|
59
|
+
/** Create a new cart */
|
|
60
|
+
Create: 'Create',
|
|
61
|
+
/** Replace discount codes */
|
|
62
|
+
DiscountCodesUpdate: 'DiscountCodesUpdate',
|
|
63
|
+
/** Replace gift card codes */
|
|
64
|
+
GiftCardCodesUpdate: 'GiftCardCodesUpdate',
|
|
65
|
+
/** Append gift card codes */
|
|
66
|
+
GiftCardCodesAdd: 'GiftCardCodesAdd',
|
|
67
|
+
/** Remove specific gift card codes */
|
|
68
|
+
GiftCardCodesRemove: 'GiftCardCodesRemove',
|
|
69
|
+
/** Add cart lines */
|
|
70
|
+
LinesAdd: 'LinesAdd',
|
|
71
|
+
/** Update cart lines (quantity / attributes) */
|
|
72
|
+
LinesUpdate: 'LinesUpdate',
|
|
73
|
+
/** Remove cart lines */
|
|
74
|
+
LinesRemove: 'LinesRemove',
|
|
75
|
+
/** Update cart note */
|
|
76
|
+
NoteUpdate: 'NoteUpdate',
|
|
77
|
+
/** Update selected delivery options */
|
|
78
|
+
SelectedDeliveryOptionsUpdate: 'SelectedDeliveryOptionsUpdate',
|
|
79
|
+
/** Set metafields */
|
|
80
|
+
MetafieldsSet: 'MetafieldsSet',
|
|
81
|
+
/** Delete metafields */
|
|
82
|
+
MetafieldsDelete: 'MetafieldsDelete',
|
|
83
|
+
};
|
|
84
|
+
// ============================================================
|
|
85
|
+
// <CartForm> 组件
|
|
86
|
+
// ============================================================
|
|
87
|
+
/** 服务端 / 客户端共享的 hidden field name — 对齐 Hydrogen */
|
|
88
|
+
const INPUT_NAME = 'cartFormInput';
|
|
89
|
+
/**
|
|
90
|
+
* CartFormFetcher Context — 让 CartForm 内部任何位置都能用 useFetcher() 拿状态
|
|
91
|
+
*
|
|
92
|
+
* Hydrogen 是用 Remix global fetcher,我们用一个本地 Context。children 既可以是
|
|
93
|
+
* render prop(推荐),也可以是普通节点 + useFetcher() 调用。
|
|
94
|
+
*/
|
|
95
|
+
const FetcherContext = React.createContext(null);
|
|
96
|
+
/**
|
|
97
|
+
* 在 <CartForm> 内部任何深度的组件里拿 fetcher 状态。
|
|
98
|
+
*
|
|
99
|
+
* <CartForm action="LinesAdd" inputs={{lines}}>
|
|
100
|
+
* <MyButton />
|
|
101
|
+
* </CartForm>
|
|
102
|
+
*
|
|
103
|
+
* function MyButton() {
|
|
104
|
+
* const fetcher = useFetcher();
|
|
105
|
+
* return <button disabled={fetcher.state !== 'idle'}>添加</button>;
|
|
106
|
+
* }
|
|
107
|
+
*/
|
|
108
|
+
export function useFetcher() {
|
|
109
|
+
const ctx = React.useContext(FetcherContext);
|
|
110
|
+
if (!ctx) {
|
|
111
|
+
throw new Error('useFetcher must be used inside a <CartForm>');
|
|
112
|
+
}
|
|
113
|
+
return ctx;
|
|
114
|
+
}
|
|
115
|
+
const CartFormBase = (props) => {
|
|
116
|
+
const { children, route = '/cart', className, action, inputs, ...rest } = props;
|
|
117
|
+
const cartCtx = useCartOptional();
|
|
118
|
+
// 序列化 { action, inputs } 一并放进 hidden input
|
|
119
|
+
// 服务端用 CartForm.getFormInput(formData) 反序列化
|
|
120
|
+
const payload = React.useMemo(() => JSON.stringify({ action, inputs }), [action, inputs]);
|
|
121
|
+
const [fetcher, setFetcher] = React.useState({
|
|
122
|
+
state: 'idle',
|
|
123
|
+
data: null,
|
|
124
|
+
error: null,
|
|
125
|
+
formData: null,
|
|
126
|
+
});
|
|
127
|
+
// hydrate 后拦截 submit;SSR 时 onSubmit 不存在 → form 原生 POST
|
|
128
|
+
const handleSubmit = React.useCallback(async (e) => {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
const form = e.currentTarget;
|
|
131
|
+
const formData = new FormData(form);
|
|
132
|
+
setFetcher({ state: 'submitting', data: null, error: null, formData });
|
|
133
|
+
// 注册到 pending store — useOptimisticCart 会看到这个 mutation 立刻应用
|
|
134
|
+
// 只对 cart line 类型的 action 做(DiscountCodesUpdate / Custom* 等不做 optimistic)
|
|
135
|
+
let unregisterPending = null;
|
|
136
|
+
if (action === 'LinesAdd' || action === 'LinesUpdate' || action === 'LinesRemove') {
|
|
137
|
+
const pendingId = `${action}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
138
|
+
unregisterPending = __registerPendingMutation({ id: pendingId, action: action, inputs });
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
// 显式标记 X-Helium-Fetch — 让服务端知道这是 JS fetch 不是原生 form POST
|
|
142
|
+
// 服务端 handleCartFormAction 看到此 header 会返回 JSON 而不是 303 redirect
|
|
143
|
+
const res = await fetch(route, {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
body: formData,
|
|
146
|
+
credentials: 'include',
|
|
147
|
+
headers: { 'Accept': 'application/json', 'X-Helium-Fetch': '1' },
|
|
148
|
+
});
|
|
149
|
+
const data = await res.json().catch(() => null);
|
|
150
|
+
if (!res.ok || data?.error || data?.userErrors?.length) {
|
|
151
|
+
throw new Error(data?.error || data?.userErrors?.[0]?.message || `HTTP ${res.status}`);
|
|
152
|
+
}
|
|
153
|
+
// 服务端约定:成功时返回 { cart: Cart }
|
|
154
|
+
// 这里直接 applyCart 到 CartProvider,避免 refetch
|
|
155
|
+
if (data?.cart && cartCtx?.applyCart) {
|
|
156
|
+
cartCtx.applyCart(data.cart);
|
|
157
|
+
}
|
|
158
|
+
setFetcher({ state: 'idle', data, error: null, formData: null });
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
setFetcher({ state: 'idle', data: null, error: err.message || String(err), formData: null });
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
unregisterPending?.();
|
|
165
|
+
}
|
|
166
|
+
}, [route, cartCtx, action, inputs]);
|
|
167
|
+
const renderedChildren = typeof children === 'function' ? children(fetcher) : children;
|
|
168
|
+
return (_jsx(FetcherContext.Provider, { value: fetcher, children: _jsxs("form", { method: "POST", action: route, onSubmit: handleSubmit, className: className, "data-cart-form": true, "data-cart-action": action, children: [_jsx("input", { type: "hidden", name: INPUT_NAME, value: payload }), renderedChildren] }) }));
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* 服务端 helper:从 request.formData() 还原出 { action, inputs }
|
|
172
|
+
*
|
|
173
|
+
* 与 Hydrogen 完全一致的 API:
|
|
174
|
+
*
|
|
175
|
+
* const formData = await request.formData();
|
|
176
|
+
* const { action, inputs } = CartForm.getFormInput(formData);
|
|
177
|
+
*/
|
|
178
|
+
function getFormInput(formData) {
|
|
179
|
+
const raw = formData.get(INPUT_NAME);
|
|
180
|
+
if (typeof raw !== 'string') {
|
|
181
|
+
throw new Error(`CartForm.getFormInput: missing field "${INPUT_NAME}" in form data`);
|
|
182
|
+
}
|
|
183
|
+
let parsed;
|
|
184
|
+
try {
|
|
185
|
+
parsed = JSON.parse(raw);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
throw new Error(`CartForm.getFormInput: field "${INPUT_NAME}" is not valid JSON`);
|
|
189
|
+
}
|
|
190
|
+
if (!parsed || typeof parsed.action !== 'string') {
|
|
191
|
+
throw new Error('CartForm.getFormInput: missing "action" in parsed payload');
|
|
192
|
+
}
|
|
193
|
+
// 合并 form 里的其它字段进 inputs(对齐 Hydrogen 的 OtherFormData 设计)
|
|
194
|
+
// <CartForm action=NoteUpdate><input name="note" /></CartForm> 这种场景
|
|
195
|
+
// FormData 里同时有 cartFormInput 和 note 两个字段,inputs.note 来自 FormData 而不是 JSON。
|
|
196
|
+
const otherInputs = {};
|
|
197
|
+
for (const [key, value] of formData.entries()) {
|
|
198
|
+
if (key === INPUT_NAME)
|
|
199
|
+
continue;
|
|
200
|
+
otherInputs[key] = value;
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
action: parsed.action,
|
|
204
|
+
inputs: { ...otherInputs, ...(parsed.inputs || {}) },
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
// 把 ACTIONS / INPUT_NAME / getFormInput 挂到 CartForm 对象上 — 对齐 Hydrogen
|
|
208
|
+
const CartForm = CartFormBase;
|
|
209
|
+
CartForm.ACTIONS = CART_FORM_ACTIONS;
|
|
210
|
+
CartForm.INPUT_NAME = INPUT_NAME;
|
|
211
|
+
CartForm.getFormInput = getFormInput;
|
|
212
|
+
export { CartForm };
|
|
213
|
+
//# sourceMappingURL=CartForm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartForm.js","sourceRoot":"","sources":["../../src/components/CartForm.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <CartLineProvider> + useCartLine() — 对齐 Hydrogen React
|
|
3
|
+
*
|
|
4
|
+
* 在 cart 渲染时,把单行 cart line 通过 Context 提供给子组件。子组件如
|
|
5
|
+
* <CartLineQuantity>、<CartLineQuantityAdjustButton>、商品图缩略图等都能直接
|
|
6
|
+
* useCartLine() 拿到 line 数据,无需 prop drilling。
|
|
7
|
+
*
|
|
8
|
+
* 用法:
|
|
9
|
+
*
|
|
10
|
+
* {cart.lines.nodes.map((line) => (
|
|
11
|
+
* <CartLineProvider key={line.id} line={line}>
|
|
12
|
+
* <CartLineImage />
|
|
13
|
+
* <CartLineTitle />
|
|
14
|
+
* <CartLineQuantity />
|
|
15
|
+
* <CartLineQuantityAdjustButton />
|
|
16
|
+
* </CartLineProvider>
|
|
17
|
+
* ))}
|
|
18
|
+
*
|
|
19
|
+
* function CartLineImage() {
|
|
20
|
+
* const line = useCartLine();
|
|
21
|
+
* return <Image data={line.merchandise.image} />;
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
24
|
+
import * as React from 'react';
|
|
25
|
+
/**
|
|
26
|
+
* Cart line 的核心字段。任何符合此 shape 的对象都能传入。
|
|
27
|
+
* 实际可以包含更多字段(merchandise / attributes / 等),由 oxygen Storefront
|
|
28
|
+
* GraphQL schema 决定。
|
|
29
|
+
*/
|
|
30
|
+
export interface CartLineLike {
|
|
31
|
+
id: string;
|
|
32
|
+
quantity: number;
|
|
33
|
+
merchandise?: {
|
|
34
|
+
id?: string;
|
|
35
|
+
title?: string;
|
|
36
|
+
image?: {
|
|
37
|
+
url: string;
|
|
38
|
+
altText?: string | null;
|
|
39
|
+
} | null;
|
|
40
|
+
price?: {
|
|
41
|
+
amount: string;
|
|
42
|
+
currencyCode: string;
|
|
43
|
+
};
|
|
44
|
+
product?: {
|
|
45
|
+
title?: string;
|
|
46
|
+
handle?: string;
|
|
47
|
+
} | null;
|
|
48
|
+
};
|
|
49
|
+
cost?: {
|
|
50
|
+
totalAmount: {
|
|
51
|
+
amount: string;
|
|
52
|
+
currencyCode: string;
|
|
53
|
+
};
|
|
54
|
+
amountPerQuantity?: {
|
|
55
|
+
amount: string;
|
|
56
|
+
currencyCode: string;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
attributes?: Array<{
|
|
60
|
+
key: string;
|
|
61
|
+
value: string;
|
|
62
|
+
}>;
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
}
|
|
65
|
+
export interface CartLineProviderProps {
|
|
66
|
+
line: CartLineLike;
|
|
67
|
+
children: React.ReactNode;
|
|
68
|
+
}
|
|
69
|
+
export declare function CartLineProvider({ line, children }: CartLineProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
70
|
+
/**
|
|
71
|
+
* 拿当前 cart line。Provider 外抛错。
|
|
72
|
+
*/
|
|
73
|
+
export declare function useCartLine(): CartLineLike;
|
|
74
|
+
/**
|
|
75
|
+
* 非 throw 版本:Provider 外返回 null。供能在 Provider 内外都工作的组件用。
|
|
76
|
+
*/
|
|
77
|
+
export declare function useCartLineOptional(): CartLineLike | null;
|
|
78
|
+
//# sourceMappingURL=CartLineProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartLineProvider.d.ts","sourceRoot":"","sources":["../../src/components/CartLineProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE;QACZ,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAC;QACxD,KAAK,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAA;SAAE,CAAC;QACjD,OAAO,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;KACtD,CAAC;IACF,IAAI,CAAC,EAAE;QACL,WAAW,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAA;SAAE,CAAC;QACtD,iBAAiB,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAA;SAAE,CAAC;KAC9D,CAAC;IACF,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAID,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,qBAAqB,2CAEzE;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,YAAY,CAM1C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,YAAY,GAAG,IAAI,CAEzD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* <CartLineProvider> + useCartLine() — 对齐 Hydrogen React
|
|
4
|
+
*
|
|
5
|
+
* 在 cart 渲染时,把单行 cart line 通过 Context 提供给子组件。子组件如
|
|
6
|
+
* <CartLineQuantity>、<CartLineQuantityAdjustButton>、商品图缩略图等都能直接
|
|
7
|
+
* useCartLine() 拿到 line 数据,无需 prop drilling。
|
|
8
|
+
*
|
|
9
|
+
* 用法:
|
|
10
|
+
*
|
|
11
|
+
* {cart.lines.nodes.map((line) => (
|
|
12
|
+
* <CartLineProvider key={line.id} line={line}>
|
|
13
|
+
* <CartLineImage />
|
|
14
|
+
* <CartLineTitle />
|
|
15
|
+
* <CartLineQuantity />
|
|
16
|
+
* <CartLineQuantityAdjustButton />
|
|
17
|
+
* </CartLineProvider>
|
|
18
|
+
* ))}
|
|
19
|
+
*
|
|
20
|
+
* function CartLineImage() {
|
|
21
|
+
* const line = useCartLine();
|
|
22
|
+
* return <Image data={line.merchandise.image} />;
|
|
23
|
+
* }
|
|
24
|
+
*/
|
|
25
|
+
import * as React from 'react';
|
|
26
|
+
const CartLineContext = React.createContext(null);
|
|
27
|
+
export function CartLineProvider({ line, children }) {
|
|
28
|
+
return _jsx(CartLineContext.Provider, { value: line, children: children });
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 拿当前 cart line。Provider 外抛错。
|
|
32
|
+
*/
|
|
33
|
+
export function useCartLine() {
|
|
34
|
+
const line = React.useContext(CartLineContext);
|
|
35
|
+
if (!line) {
|
|
36
|
+
throw new Error('useCartLine must be used inside <CartLineProvider>');
|
|
37
|
+
}
|
|
38
|
+
return line;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 非 throw 版本:Provider 外返回 null。供能在 Provider 内外都工作的组件用。
|
|
42
|
+
*/
|
|
43
|
+
export function useCartLineOptional() {
|
|
44
|
+
return React.useContext(CartLineContext);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=CartLineProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartLineProvider.js","sourceRoot":"","sources":["../../src/components/CartLineProvider.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAyB/B,MAAM,eAAe,GAAG,KAAK,CAAC,aAAa,CAAsB,IAAI,CAAC,CAAC;AAOvE,MAAM,UAAU,gBAAgB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAyB;IACxE,OAAO,KAAC,eAAe,CAAC,QAAQ,IAAC,KAAK,EAAE,IAAI,YAAG,QAAQ,GAA4B,CAAC;AACtF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;IAC/C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,KAAK,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <CartLineQuantity> — 对齐 Hydrogen React
|
|
3
|
+
*
|
|
4
|
+
* 渲染当前 cart line 的数量。从 <CartLineProvider> Context 拿 line.quantity。
|
|
5
|
+
*
|
|
6
|
+
* 用法:
|
|
7
|
+
* <CartLineProvider line={line}>
|
|
8
|
+
* 数量:<CartLineQuantity />
|
|
9
|
+
* </CartLineProvider>
|
|
10
|
+
*
|
|
11
|
+
* 自定义渲染元素:
|
|
12
|
+
* <CartLineQuantity as="strong" className="qty-badge" />
|
|
13
|
+
*/
|
|
14
|
+
import * as React from 'react';
|
|
15
|
+
export interface CartLineQuantityProps {
|
|
16
|
+
/** 渲染标签,默认 span */
|
|
17
|
+
as?: keyof JSX.IntrinsicElements;
|
|
18
|
+
/** className */
|
|
19
|
+
className?: string;
|
|
20
|
+
/** 子节点 — 数字会显示在 children 之前;通常不传 */
|
|
21
|
+
children?: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
export declare function CartLineQuantity(props: CartLineQuantityProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
//# sourceMappingURL=CartLineQuantity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartLineQuantity.d.ts","sourceRoot":"","sources":["../../src/components/CartLineQuantity.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,qBAAqB;IACpC,mBAAmB;IACnB,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,iBAAiB,CAAC;IACjC,gBAAgB;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,2CAU5D"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCartLine } from './CartLineProvider';
|
|
3
|
+
export function CartLineQuantity(props) {
|
|
4
|
+
const { as, className, children } = props;
|
|
5
|
+
const line = useCartLine();
|
|
6
|
+
const Tag = as || 'span';
|
|
7
|
+
return (_jsxs(Tag, { className: className, "data-cart-line-quantity": true, children: [line.quantity, children] }));
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=CartLineQuantity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartLineQuantity.js","sourceRoot":"","sources":["../../src/components/CartLineQuantity.tsx"],"names":[],"mappings":";AAeA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAWjD,MAAM,UAAU,gBAAgB,CAAC,KAA4B;IAC3D,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAC1C,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAQ,EAAE,IAAI,MAAM,CAAC;IAC9B,OAAO,CACL,MAAC,GAAG,IAAC,SAAS,EAAE,SAAS,8CACtB,IAAI,CAAC,QAAQ,EACb,QAAQ,IACL,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DiscountSelector.d.ts","sourceRoot":"","sources":["../../src/components/DiscountSelector.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,uBAAuB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1C,YAAY;IACZ,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAC;CAC5J;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,
|
|
1
|
+
{"version":3,"file":"DiscountSelector.d.ts","sourceRoot":"","sources":["../../src/components/DiscountSelector.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,uBAAuB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1C,YAAY;IACZ,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAC;CAC5J;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,2CAsF5D"}
|