@arcblock/ux 2.12.45 → 2.12.47

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.
@@ -143,6 +143,7 @@ function Dashboard({
143
143
  })
144
144
  }), /*#__PURE__*/_jsxs(Box, {
145
145
  className: "dashboard-main",
146
+ id: "arc__dashboard-main",
146
147
  children: [showToggleButton && /*#__PURE__*/_jsx(Box, {
147
148
  sx: {
148
149
  position: 'absolute',
@@ -170,6 +171,7 @@ function Dashboard({
170
171
  })
171
172
  }), /*#__PURE__*/_jsx(Container, {
172
173
  className: "dashboard-content",
174
+ id: "arc__dashboard-content",
173
175
  ...(fullWidth && {
174
176
  maxWidth: false
175
177
  }),
@@ -12,7 +12,8 @@ export interface PhoneInputProps extends Omit<TextFieldProps, 'value' | 'onChang
12
12
  preview?: boolean;
13
13
  allowDial?: boolean;
14
14
  }
15
- export declare function validatePhoneNumber(phone: string): boolean;
15
+ export declare function validatePhoneNumber(phone: string, iso2: CountryIso2, dialCode?: string): boolean;
16
16
  export declare function getDialCodeByCountry(iso2: CountryIso2): string;
17
17
  export declare function getCountryNameByCountry(iso2: CountryIso2): string;
18
+ export declare function detectCountryFromPhone(phoneNumber?: string): CountryIso2 | undefined;
18
19
  export default function PhoneInput({ value, onChange, placeholder, countryDisplayOptions, preview, allowDial, ...props }: PhoneInputProps): import("react/jsx-runtime").JSX.Element;
@@ -1,29 +1,28 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useMemo, useState } from 'react';
3
3
  import { Box, TextField, Typography } from '@mui/material';
4
+ import { parsePhoneNumber } from 'awesome-phonenumber';
4
5
  import { defaultCountries, usePhoneInput, parseCountry } from 'react-international-phone';
5
- import isMobilePhone from 'validator/lib/isMobilePhone';
6
6
  import CountrySelect from './country-select';
7
7
  import { mergeSx } from '../Util/style';
8
- export function validatePhoneNumber(phone) {
8
+ export function validatePhoneNumber(phone, iso2, dialCode = '') {
9
9
  // 如果没有输入电话号码,返回false
10
10
  if (!phone) return false;
11
11
 
12
- // 检查是否只有国家区号
13
- // 电话号码格式通常为 +区号 后跟电话号码
14
12
  // 如果去除所有格式化字符后,只剩下区号,则认为只有区号
15
13
  const cleanedPhone = phone.replace(/[\s\-()]+/g, '');
16
-
17
- // 使用正则表达式匹配:以+开头,后面全是数字,但长度不超过5(大多数国家区号在1-4位)
14
+ const phoneDialCode = dialCode || getDialCodeByCountry(iso2);
18
15
  // 这表示电话号码只有区号部分
19
- if (/^\+\d{1,5}$/.test(cleanedPhone)) {
16
+ if (getDialCodeWithoutPlus(phoneDialCode) === getDialCodeWithoutPlus(cleanedPhone)) {
20
17
  return true; // 如果只有区号,则视为有效
21
18
  }
22
19
 
23
20
  // 否则使用validator进行完整校验
24
- return isMobilePhone(cleanedPhone, 'any', {
25
- strictMode: true
21
+ const pn = parsePhoneNumber(phone, {
22
+ regionCode: iso2
26
23
  });
24
+ const cleanedParsedPhone = pn.number?.input.replace(/[\s\-()]+/g, '');
25
+ return pn.valid && (pn.type === 'mobile' || pn.type === 'fixed-line-or-mobile') && cleanedParsedPhone === pn.number?.e164;
27
26
  }
28
27
 
29
28
  // 从带区号的电话号码中提取纯号码部分
@@ -32,7 +31,7 @@ function extractPhoneWithoutCode(phone, dialCode) {
32
31
  // 先去除区号
33
32
  const phoneWithoutCode = phone.replace(new RegExp(`^\\+${dialCode}`), '');
34
33
  // 去除非数字字符,但保留括号
35
- return phoneWithoutCode.replace(/[^\d]/g, '');
34
+ return phoneWithoutCode;
36
35
  }
37
36
 
38
37
  // 添加区号到纯号码
@@ -67,6 +66,44 @@ export function getDialCodeByCountry(iso2) {
67
66
  export function getCountryNameByCountry(iso2) {
68
67
  return getCountryInfoByIso2(iso2, 'name') || '';
69
68
  }
69
+
70
+ // 根据手机号识别国家,返回国家的 iso2 码
71
+ // 如果手机号带有区号,通过 parsePhoneNumber 识别
72
+ // 如果手机号不带区号,通过碰撞识别
73
+ export function detectCountryFromPhone(phoneNumber) {
74
+ try {
75
+ if (!phoneNumber) return undefined;
76
+
77
+ // 如果已经带了+号,尝试从前缀匹配
78
+ if (phoneNumber.startsWith('+')) {
79
+ const parsedPhone = parsePhoneNumber(phoneNumber);
80
+ if (parsedPhone.valid) {
81
+ return parsedPhone.regionCode.toLowerCase();
82
+ }
83
+ for (const country of defaultCountries) {
84
+ const parsed = parseCountry(country);
85
+ if (phoneNumber.startsWith(`+${parsed.dialCode}`)) {
86
+ const phoneWithCode = phoneNumber;
87
+ // 验证该号码是否有效
88
+ if (validatePhoneNumber(phoneWithCode, parsed.dialCode, parsed.iso2)) {
89
+ return parsed.iso2;
90
+ }
91
+ }
92
+ }
93
+ } else {
94
+ for (const country of defaultCountries) {
95
+ const parsed = parseCountry(country);
96
+ const phoneWithCode = `+${parsed.dialCode}${phoneNumber}`;
97
+ if (validatePhoneNumber(phoneWithCode, parsed.dialCode, parsed.iso2)) {
98
+ return parsed.iso2;
99
+ }
100
+ }
101
+ }
102
+ return undefined;
103
+ } catch {
104
+ return undefined;
105
+ }
106
+ }
70
107
  export default function PhoneInput({
71
108
  value = {
72
109
  country: 'us',
@@ -131,7 +168,7 @@ export default function PhoneInput({
131
168
  });
132
169
 
133
170
  // 从完整电话号码中提取不带区号的部分用于显示,并去除格式化字符
134
- const displayPhone = useMemo(() => phone ? phone.replace(/[^\d]/g, '') : extractPhoneWithoutCode(value.phone, currentDialCode), [phone, value.phone, currentDialCode]);
171
+ const displayPhone = useMemo(() => phone || extractPhoneWithoutCode(value.phone, currentDialCode), [phone, value.phone, currentDialCode]);
135
172
 
136
173
  // 处理国家变更
137
174
  const onCountryChange = newCountry => {
@@ -141,7 +178,7 @@ export default function PhoneInput({
141
178
 
142
179
  // 预览模式
143
180
  if (preview) {
144
- const isValid = phone && validatePhoneNumber(phone);
181
+ const isValid = phone && validatePhoneNumber(phone, country);
145
182
  const canDial = allowDial && isValid;
146
183
  return /*#__PURE__*/_jsxs(Box, {
147
184
  display: "flex",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "2.12.45",
3
+ "version": "2.12.47",
4
4
  "description": "Common used react components for arcblock products",
5
5
  "keywords": [
6
6
  "react",
@@ -68,12 +68,12 @@
68
68
  "react": ">=18.2.0",
69
69
  "react-router-dom": ">=6.22.3"
70
70
  },
71
- "gitHead": "dba0f3cdf67f80855a492dc43a7485724a2d7940",
71
+ "gitHead": "f8b62d37754f4254c8f2961eb778ca2ce71264ee",
72
72
  "dependencies": {
73
73
  "@arcblock/did-motif": "^1.1.13",
74
- "@arcblock/icons": "^2.12.45",
75
- "@arcblock/nft-display": "^2.12.45",
76
- "@arcblock/react-hooks": "^2.12.45",
74
+ "@arcblock/icons": "^2.12.47",
75
+ "@arcblock/nft-display": "^2.12.47",
76
+ "@arcblock/react-hooks": "^2.12.47",
77
77
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
78
78
  "@fontsource/inter": "^5.0.16",
79
79
  "@fontsource/ubuntu-mono": "^5.0.18",
@@ -87,6 +87,7 @@
87
87
  "@types/dompurify": "^3.2.0",
88
88
  "@types/mui-datatables": "^4.3.12",
89
89
  "ahooks": "^3.7.10",
90
+ "awesome-phonenumber": "^7.4.0",
90
91
  "axios": "^1.7.5",
91
92
  "base64-url": "^2.3.3",
92
93
  "copy-to-clipboard": "^3.3.2",
@@ -136,7 +136,7 @@ function Dashboard({
136
136
  </Box>
137
137
  )}
138
138
  </Hidden>
139
- <Box className="dashboard-main">
139
+ <Box className="dashboard-main" id="arc__dashboard-main">
140
140
  {showToggleButton && (
141
141
  <Box
142
142
  sx={{
@@ -161,7 +161,7 @@ function Dashboard({
161
161
  )}
162
162
  </Box>
163
163
  )}
164
- <Container className="dashboard-content" {...(fullWidth && { maxWidth: false })}>
164
+ <Container className="dashboard-content" id="arc__dashboard-content" {...(fullWidth && { maxWidth: false })}>
165
165
  {children}
166
166
  </Container>
167
167
  {footerVisible && <Footer {...footerProps} />}
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useMemo, useState } from 'react';
2
2
  import { Box, TextField, TextFieldProps, Typography } from '@mui/material';
3
+ import { parsePhoneNumber } from 'awesome-phonenumber';
3
4
  import { defaultCountries, CountryIso2, usePhoneInput, parseCountry } from 'react-international-phone';
4
- import isMobilePhone from 'validator/lib/isMobilePhone';
5
5
  import CountrySelect, { CountryDisplayOptions } from './country-select';
6
6
  import { mergeSx } from '../Util/style';
7
7
 
@@ -18,23 +18,25 @@ export interface PhoneInputProps extends Omit<TextFieldProps, 'value' | 'onChang
18
18
  allowDial?: boolean; // 是否允许拨号, 只在 preview 为 true 时有效
19
19
  }
20
20
 
21
- export function validatePhoneNumber(phone: string): boolean {
21
+ export function validatePhoneNumber(phone: string, iso2: CountryIso2, dialCode: string = ''): boolean {
22
22
  // 如果没有输入电话号码,返回false
23
23
  if (!phone) return false;
24
24
 
25
- // 检查是否只有国家区号
26
- // 电话号码格式通常为 +区号 后跟电话号码
27
25
  // 如果去除所有格式化字符后,只剩下区号,则认为只有区号
28
26
  const cleanedPhone = phone.replace(/[\s\-()]+/g, '');
29
27
 
30
- // 使用正则表达式匹配:以+开头,后面全是数字,但长度不超过5(大多数国家区号在1-4位)
28
+ const phoneDialCode = dialCode || getDialCodeByCountry(iso2);
31
29
  // 这表示电话号码只有区号部分
32
- if (/^\+\d{1,5}$/.test(cleanedPhone)) {
30
+ if (getDialCodeWithoutPlus(phoneDialCode) === getDialCodeWithoutPlus(cleanedPhone)) {
33
31
  return true; // 如果只有区号,则视为有效
34
32
  }
35
33
 
36
34
  // 否则使用validator进行完整校验
37
- return isMobilePhone(cleanedPhone, 'any', { strictMode: true });
35
+ const pn = parsePhoneNumber(phone, { regionCode: iso2 });
36
+ const cleanedParsedPhone = pn.number?.input.replace(/[\s\-()]+/g, '');
37
+ return (
38
+ pn.valid && (pn.type === 'mobile' || pn.type === 'fixed-line-or-mobile') && cleanedParsedPhone === pn.number?.e164
39
+ );
38
40
  }
39
41
 
40
42
  // 从带区号的电话号码中提取纯号码部分
@@ -43,7 +45,7 @@ function extractPhoneWithoutCode(phone: string, dialCode: string): string {
43
45
  // 先去除区号
44
46
  const phoneWithoutCode = phone.replace(new RegExp(`^\\+${dialCode}`), '');
45
47
  // 去除非数字字符,但保留括号
46
- return phoneWithoutCode.replace(/[^\d]/g, '');
48
+ return phoneWithoutCode;
47
49
  }
48
50
 
49
51
  // 添加区号到纯号码
@@ -83,6 +85,44 @@ export function getCountryNameByCountry(iso2: CountryIso2): string {
83
85
  return getCountryInfoByIso2(iso2, 'name') || '';
84
86
  }
85
87
 
88
+ // 根据手机号识别国家,返回国家的 iso2 码
89
+ // 如果手机号带有区号,通过 parsePhoneNumber 识别
90
+ // 如果手机号不带区号,通过碰撞识别
91
+ export function detectCountryFromPhone(phoneNumber?: string): CountryIso2 | undefined {
92
+ try {
93
+ if (!phoneNumber) return undefined;
94
+
95
+ // 如果已经带了+号,尝试从前缀匹配
96
+ if (phoneNumber.startsWith('+')) {
97
+ const parsedPhone = parsePhoneNumber(phoneNumber);
98
+ if (parsedPhone.valid) {
99
+ return parsedPhone.regionCode.toLowerCase() as CountryIso2;
100
+ }
101
+ for (const country of defaultCountries) {
102
+ const parsed = parseCountry(country);
103
+ if (phoneNumber.startsWith(`+${parsed.dialCode}`)) {
104
+ const phoneWithCode = phoneNumber;
105
+ // 验证该号码是否有效
106
+ if (validatePhoneNumber(phoneWithCode, parsed.dialCode, parsed.iso2)) {
107
+ return parsed.iso2;
108
+ }
109
+ }
110
+ }
111
+ } else {
112
+ for (const country of defaultCountries) {
113
+ const parsed = parseCountry(country);
114
+ const phoneWithCode = `+${parsed.dialCode}${phoneNumber}`;
115
+ if (validatePhoneNumber(phoneWithCode, parsed.dialCode, parsed.iso2)) {
116
+ return parsed.iso2;
117
+ }
118
+ }
119
+ }
120
+ return undefined;
121
+ } catch {
122
+ return undefined;
123
+ }
124
+ }
125
+
86
126
  export default function PhoneInput({
87
127
  value = { country: 'us', phone: '' },
88
128
  onChange,
@@ -148,7 +188,7 @@ export default function PhoneInput({
148
188
 
149
189
  // 从完整电话号码中提取不带区号的部分用于显示,并去除格式化字符
150
190
  const displayPhone = useMemo(
151
- () => (phone ? phone.replace(/[^\d]/g, '') : extractPhoneWithoutCode(value.phone, currentDialCode)),
191
+ () => phone || extractPhoneWithoutCode(value.phone, currentDialCode),
152
192
  [phone, value.phone, currentDialCode]
153
193
  );
154
194
 
@@ -160,7 +200,7 @@ export default function PhoneInput({
160
200
 
161
201
  // 预览模式
162
202
  if (preview) {
163
- const isValid = phone && validatePhoneNumber(phone);
203
+ const isValid = phone && validatePhoneNumber(phone, country);
164
204
  const canDial = allowDial && isValid;
165
205
 
166
206
  return (