@shopbb/helium 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +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"}
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;AAwCD,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,2CA6CtC"}
@@ -1,41 +1,59 @@
1
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',
2
+ /**
3
+ * currency symbol。手写一张表,不用 Intl.NumberFormat —— 因为它在 Cloudflare Workers V8
4
+ * 和浏览器 V8 之间输出可能不同(货币符号位置、本地化分隔符),导致 SSR hydration mismatch。
5
+ *
6
+ * Shopify Hydrogen 的 <Money> 用同款手写方案,原因相同。
7
+ */
8
+ const CURRENCY_SYMBOL = {
9
+ CNY: '¥',
10
+ USD: '$',
11
+ EUR: '€',
12
+ GBP: '£',
13
+ JPY: '¥',
14
+ HKD: 'HK$',
15
+ KRW: '₩',
11
16
  };
17
+ /**
18
+ * 按 1000 分组加 thousand-separator。
19
+ * 中文/英文 locale 都是逗号;其他 locale 暂用逗号(如需 . 分隔,加 locale 表)。
20
+ */
21
+ function formatAmount(amount, minDecimals, maxDecimals) {
22
+ if (!Number.isFinite(amount))
23
+ return '0';
24
+ const negative = amount < 0;
25
+ const abs = Math.abs(amount);
26
+ // 强制小数位数
27
+ const fixed = abs.toFixed(maxDecimals);
28
+ const [intPart, decPart = ''] = fixed.split('.');
29
+ // 加千分位
30
+ const intWithSep = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
31
+ // 去掉多余的尾零(保留最低 minDecimals 位)
32
+ let trimmedDec = decPart;
33
+ while (trimmedDec.length > minDecimals && trimmedDec.endsWith('0')) {
34
+ trimmedDec = trimmedDec.slice(0, -1);
35
+ }
36
+ const out = trimmedDec ? `${intWithSep}.${trimmedDec}` : intWithSep;
37
+ return negative ? `-${out}` : out;
38
+ }
12
39
  export function Money(props) {
13
- const { data, withoutCurrency, withoutTrailingZeros, locale: localeProp, as, measurement, ...rest } = props;
40
+ const { data, withoutCurrency, withoutTrailingZeros, locale: _localeProp, as, measurement, ...rest } = props;
14
41
  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';
42
+ // locale 现在不影响输出(避免 SSR 不一致)。保留 prop 给未来可能扩展。
20
43
  const amount = Number(data.amount);
44
+ const minDec = withoutTrailingZeros && Number.isInteger(amount) ? 0 : 2;
45
+ const numStr = formatAmount(amount, minDec, 2);
46
+ const symbol = CURRENCY_SYMBOL[data.currencyCode];
21
47
  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);
48
+ if (withoutCurrency) {
49
+ formatted = numStr;
50
+ }
51
+ else if (symbol) {
52
+ formatted = `${symbol}${numStr}`;
35
53
  }
36
- catch {
37
- // Fallback:Intl 不支持的 currencyCode
38
- formatted = withoutCurrency ? amount.toFixed(2) : `${data.currencyCode} ${amount.toFixed(2)}`;
54
+ else {
55
+ // 未识别 currency code: 用 "USD 99.00" 形式
56
+ formatted = `${data.currencyCode} ${numStr}`;
39
57
  }
40
58
  // measurement suffix(unit price)
41
59
  let suffix = '';
@@ -1 +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"}
1
+ {"version":3,"file":"Money.js","sourceRoot":"","sources":["../../src/components/Money.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopbb/helium",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "shopbb storefront framework — components, React SSR, GraphQL client, cart handler, cache for Cloudflare Workers",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,53 +43,70 @@ export interface MoneyProps extends React.HTMLAttributes<HTMLElement> {
43
43
  measurement?: MoneyMeasurement;
44
44
  }
45
45
 
46
- // currencyCode → 默认 locale 推断
47
- const DEFAULT_LOCALE_BY_CURRENCY: Record<string, string> = {
48
- CNY: 'zh-CN',
49
- USD: 'en-US',
50
- EUR: 'de-DE',
51
- GBP: 'en-GB',
52
- JPY: 'ja-JP',
53
- KRW: 'ko-KR',
46
+ /**
47
+ * currency symbol。手写一张表,不用 Intl.NumberFormat —— 因为它在 Cloudflare Workers V8
48
+ * 和浏览器 V8 之间输出可能不同(货币符号位置、本地化分隔符),导致 SSR hydration mismatch。
49
+ *
50
+ * Shopify Hydrogen 的 <Money> 用同款手写方案,原因相同。
51
+ */
52
+ const CURRENCY_SYMBOL: Record<string, string> = {
53
+ CNY: '¥',
54
+ USD: '$',
55
+ EUR: '€',
56
+ GBP: '£',
57
+ JPY: '¥',
58
+ HKD: 'HK$',
59
+ KRW: '₩',
54
60
  };
55
61
 
62
+ /**
63
+ * 按 1000 分组加 thousand-separator。
64
+ * 中文/英文 locale 都是逗号;其他 locale 暂用逗号(如需 . 分隔,加 locale 表)。
65
+ */
66
+ function formatAmount(amount: number, minDecimals: number, maxDecimals: number): string {
67
+ if (!Number.isFinite(amount)) return '0';
68
+ const negative = amount < 0;
69
+ const abs = Math.abs(amount);
70
+ // 强制小数位数
71
+ const fixed = abs.toFixed(maxDecimals);
72
+ const [intPart, decPart = ''] = fixed.split('.');
73
+ // 加千分位
74
+ const intWithSep = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
75
+ // 去掉多余的尾零(保留最低 minDecimals 位)
76
+ let trimmedDec = decPart;
77
+ while (trimmedDec.length > minDecimals && trimmedDec.endsWith('0')) {
78
+ trimmedDec = trimmedDec.slice(0, -1);
79
+ }
80
+ const out = trimmedDec ? `${intWithSep}.${trimmedDec}` : intWithSep;
81
+ return negative ? `-${out}` : out;
82
+ }
83
+
56
84
  export function Money(props: MoneyProps) {
57
85
  const {
58
86
  data,
59
87
  withoutCurrency,
60
88
  withoutTrailingZeros,
61
- locale: localeProp,
89
+ locale: _localeProp,
62
90
  as,
63
91
  measurement,
64
92
  ...rest
65
93
  } = props;
66
94
 
67
95
  const Tag: any = as || 'span';
68
- const shop = useShopOptional();
69
- const locale =
70
- localeProp ||
71
- shop?.locale ||
72
- DEFAULT_LOCALE_BY_CURRENCY[data.currencyCode] ||
73
- 'en-US';
96
+ // locale 现在不影响输出(避免 SSR 不一致)。保留 prop 给未来可能扩展。
74
97
  const amount = Number(data.amount);
98
+ const minDec = withoutTrailingZeros && Number.isInteger(amount) ? 0 : 2;
99
+ const numStr = formatAmount(amount, minDec, 2);
100
+ const symbol = CURRENCY_SYMBOL[data.currencyCode];
75
101
 
76
102
  let formatted: string;
77
- try {
78
- const opts: Intl.NumberFormatOptions = withoutCurrency
79
- ? {
80
- minimumFractionDigits: withoutTrailingZeros && Number.isInteger(amount) ? 0 : 2,
81
- maximumFractionDigits: 2,
82
- }
83
- : {
84
- style: 'currency',
85
- currency: data.currencyCode,
86
- minimumFractionDigits: withoutTrailingZeros && Number.isInteger(amount) ? 0 : 2,
87
- maximumFractionDigits: 2,
88
- };
89
- formatted = new Intl.NumberFormat(locale, opts).format(amount);
90
- } catch {
91
- // Fallback:Intl 不支持的 currencyCode
92
- formatted = withoutCurrency ? amount.toFixed(2) : `${data.currencyCode} ${amount.toFixed(2)}`;
103
+ if (withoutCurrency) {
104
+ formatted = numStr;
105
+ } else if (symbol) {
106
+ formatted = `${symbol}${numStr}`;
107
+ } else {
108
+ // 未识别 currency code: 用 "USD 99.00" 形式
109
+ formatted = `${data.currencyCode} ${numStr}`;
93
110
  }
94
111
 
95
112
  // measurement suffix(unit price)