@longline/aqua-ui 1.0.337 → 1.0.339
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/formatters/PhoneFormatter/PhoneFormatter.d.ts +25 -0
- package/formatters/PhoneFormatter/PhoneFormatter.js +34 -0
- package/formatters/PhoneFormatter/index.d.ts +1 -0
- package/formatters/PhoneFormatter/index.js +1 -0
- package/helper/phoneNumber.d.ts +31 -0
- package/helper/phoneNumber.js +142 -0
- package/inputs/Editor/Editor.js +13 -4
- package/inputs/PhoneInput/PhoneInput.d.ts +92 -0
- package/inputs/PhoneInput/PhoneInput.js +113 -0
- package/inputs/PhoneInput/index.d.ts +2 -0
- package/inputs/PhoneInput/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
interface IPhoneFormatterProps {
|
|
3
|
+
/**
|
|
4
|
+
* Phone number in E.164 format, e.g. `"+351910306792"`.
|
|
5
|
+
*/
|
|
6
|
+
value: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Displays a phone number alongside the flag of the country its international
|
|
10
|
+
* dialing prefix belongs to. The country is derived from the number itself
|
|
11
|
+
* (the shared `+1` North American range is disambiguated by area code), so
|
|
12
|
+
* only the number is needed — there is nothing else to store.
|
|
13
|
+
*
|
|
14
|
+
* ## Usage
|
|
15
|
+
*
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <PhoneFormatter value="+351910306792"/>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
declare const PhoneFormatter: {
|
|
21
|
+
({ value }: IPhoneFormatterProps): React.JSX.Element;
|
|
22
|
+
displayName: string;
|
|
23
|
+
};
|
|
24
|
+
export { PhoneFormatter };
|
|
25
|
+
export type { IPhoneFormatterProps };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
|
|
2
|
+
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
|
|
3
|
+
return cooked;
|
|
4
|
+
};
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import styled from 'styled-components';
|
|
7
|
+
import { CountryFormatter } from '../CountryFormatter';
|
|
8
|
+
import { parseE164 } from '../../helper/phoneNumber';
|
|
9
|
+
/**
|
|
10
|
+
* Displays a phone number alongside the flag of the country its international
|
|
11
|
+
* dialing prefix belongs to. The country is derived from the number itself
|
|
12
|
+
* (the shared `+1` North American range is disambiguated by area code), so
|
|
13
|
+
* only the number is needed — there is nothing else to store.
|
|
14
|
+
*
|
|
15
|
+
* ## Usage
|
|
16
|
+
*
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <PhoneFormatter value="+351910306792"/>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
var PhoneFormatter = function (_a) {
|
|
22
|
+
var value = _a.value;
|
|
23
|
+
if (!value)
|
|
24
|
+
return null;
|
|
25
|
+
var iso = parseE164(value).iso;
|
|
26
|
+
return (React.createElement(Wrapper, null,
|
|
27
|
+
iso && React.createElement(CountryFormatter, { type: "flag", value: iso }),
|
|
28
|
+
React.createElement(Number, null, value)));
|
|
29
|
+
};
|
|
30
|
+
PhoneFormatter.displayName = 'PhoneFormatter';
|
|
31
|
+
var Wrapper = styled.div(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 6px;\n"], ["\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 6px;\n"])));
|
|
32
|
+
var Number = styled.div(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n text-overflow: ellipsis;\n overflow: hidden;\n"], ["\n text-overflow: ellipsis;\n overflow: hidden;\n"])));
|
|
33
|
+
export { PhoneFormatter };
|
|
34
|
+
var templateObject_1, templateObject_2;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PhoneFormatter';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PhoneFormatter';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISO 3166-1 alpha-2 country code → ITU country calling code (digits, no '+').
|
|
3
|
+
*
|
|
4
|
+
* Country calling codes are a prefix-free set by ITU design (no code is a
|
|
5
|
+
* prefix of another), so an E.164 number can be split into (code, national)
|
|
6
|
+
* deterministically by longest-prefix match — see parseE164().
|
|
7
|
+
*/
|
|
8
|
+
export declare const DIAL_BY_ISO: Record<string, string>;
|
|
9
|
+
/** Calling code (digits) → representative ISO country code. */
|
|
10
|
+
export declare const ISO_BY_DIAL: Record<string, string>;
|
|
11
|
+
export declare const digitsOnly: (s: string | null | undefined) => string;
|
|
12
|
+
export interface IParsedPhone {
|
|
13
|
+
/** Representative ISO country for the matched calling code, or null. */
|
|
14
|
+
iso: string | null;
|
|
15
|
+
/** Calling code digits (e.g. "351"), or null if none matched. */
|
|
16
|
+
code: string | null;
|
|
17
|
+
/** The national part (digits after the calling code). */
|
|
18
|
+
national: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Split an E.164 string ("+351123456789") into its calling code and national
|
|
22
|
+
* part. Relies on calling codes being prefix-free, so the first prefix (of
|
|
23
|
+
* length 1–3) that is a known code is the unique correct one.
|
|
24
|
+
*/
|
|
25
|
+
export declare const parseE164: (value: string | null | undefined) => IParsedPhone;
|
|
26
|
+
/**
|
|
27
|
+
* Build an E.164 string from a selected country and a national part. Returns
|
|
28
|
+
* '' when there is no national number, so the field stays empty/optional
|
|
29
|
+
* rather than persisting a bare "+44".
|
|
30
|
+
*/
|
|
31
|
+
export declare const assembleE164: (iso: string, national: string) => string;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISO 3166-1 alpha-2 country code → ITU country calling code (digits, no '+').
|
|
3
|
+
*
|
|
4
|
+
* Country calling codes are a prefix-free set by ITU design (no code is a
|
|
5
|
+
* prefix of another), so an E.164 number can be split into (code, national)
|
|
6
|
+
* deterministically by longest-prefix match — see parseE164().
|
|
7
|
+
*/
|
|
8
|
+
export var DIAL_BY_ISO = {
|
|
9
|
+
AC: '247', AD: '376', AE: '971', AF: '93', AG: '1', AI: '1', AL: '355',
|
|
10
|
+
AM: '374', AO: '244', AR: '54', AS: '1', AT: '43', AU: '61', AW: '297',
|
|
11
|
+
AX: '358', AZ: '994',
|
|
12
|
+
BA: '387', BB: '1', BD: '880', BE: '32', BF: '226', BG: '359', BH: '973',
|
|
13
|
+
BI: '257', BJ: '229', BL: '590', BM: '1', BN: '673', BO: '591', BQ: '599',
|
|
14
|
+
BR: '55', BS: '1', BT: '975', BW: '267', BY: '375', BZ: '501',
|
|
15
|
+
CA: '1', CC: '61', CD: '243', CF: '236', CG: '242', CH: '41', CI: '225',
|
|
16
|
+
CK: '682', CL: '56', CM: '237', CN: '86', CO: '57', CR: '506', CU: '53',
|
|
17
|
+
CV: '238', CW: '599', CX: '61', CY: '357', CZ: '420',
|
|
18
|
+
DE: '49', DJ: '253', DK: '45', DM: '1', DO: '1', DZ: '213',
|
|
19
|
+
EC: '593', EE: '372', EG: '20', EH: '212', ER: '291', ES: '34', ET: '251',
|
|
20
|
+
FI: '358', FJ: '679', FK: '500', FM: '691', FO: '298', FR: '33',
|
|
21
|
+
GA: '241', GB: '44', GD: '1', GE: '995', GF: '594', GG: '44', GH: '233',
|
|
22
|
+
GI: '350', GL: '299', GM: '220', GN: '224', GP: '590', GQ: '240', GR: '30',
|
|
23
|
+
GT: '502', GU: '1', GW: '245', GY: '592',
|
|
24
|
+
HK: '852', HN: '504', HR: '385', HT: '509', HU: '36',
|
|
25
|
+
ID: '62', IE: '353', IL: '972', IM: '44', IN: '91', IO: '246', IQ: '964',
|
|
26
|
+
IR: '98', IS: '354', IT: '39',
|
|
27
|
+
JE: '44', JM: '1', JO: '962', JP: '81',
|
|
28
|
+
KE: '254', KG: '996', KH: '855', KI: '686', KM: '269', KN: '1', KP: '850',
|
|
29
|
+
KR: '82', KW: '965', KY: '1', KZ: '7',
|
|
30
|
+
LA: '856', LB: '961', LC: '1', LI: '423', LK: '94', LR: '231', LS: '266',
|
|
31
|
+
LT: '370', LU: '352', LV: '371', LY: '218',
|
|
32
|
+
MA: '212', MC: '377', MD: '373', ME: '382', MF: '590', MG: '261', MH: '692',
|
|
33
|
+
MK: '389', ML: '223', MM: '95', MN: '976', MO: '853', MP: '1', MQ: '596',
|
|
34
|
+
MR: '222', MS: '1', MT: '356', MU: '230', MV: '960', MW: '265', MX: '52',
|
|
35
|
+
MY: '60', MZ: '258',
|
|
36
|
+
NA: '264', NC: '687', NE: '227', NF: '672', NG: '234', NI: '505', NL: '31',
|
|
37
|
+
NO: '47', NP: '977', NR: '674', NU: '683', NZ: '64',
|
|
38
|
+
OM: '968',
|
|
39
|
+
PA: '507', PE: '51', PF: '689', PG: '675', PH: '63', PK: '92', PL: '48',
|
|
40
|
+
PM: '508', PN: '64', PR: '1', PS: '970', PT: '351', PW: '680', PY: '595',
|
|
41
|
+
QA: '974',
|
|
42
|
+
RE: '262', RO: '40', RS: '381', RU: '7', RW: '250',
|
|
43
|
+
SA: '966', SB: '677', SC: '248', SD: '249', SE: '46', SG: '65', SH: '290',
|
|
44
|
+
SI: '386', SJ: '47', SK: '421', SL: '232', SM: '378', SN: '221', SO: '252',
|
|
45
|
+
SR: '597', SS: '211', ST: '239', SV: '503', SX: '1', SY: '963', SZ: '268',
|
|
46
|
+
TC: '1', TD: '235', TG: '228', TH: '66', TJ: '992', TK: '690', TL: '670',
|
|
47
|
+
TM: '993', TN: '216', TO: '676', TR: '90', TT: '1', TV: '688', TW: '886',
|
|
48
|
+
TZ: '255',
|
|
49
|
+
UA: '380', UG: '256', US: '1', UY: '598', UZ: '998',
|
|
50
|
+
VA: '379', VC: '1', VE: '58', VG: '1', VI: '1', VN: '84', VU: '678',
|
|
51
|
+
WF: '681', WS: '685',
|
|
52
|
+
XK: '383',
|
|
53
|
+
YE: '967', YT: '262',
|
|
54
|
+
ZA: '27', ZM: '260', ZW: '263',
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Canonical country shown for a calling code when several countries share it
|
|
58
|
+
* (e.g. +1 → US, +44 → GB). We only store the full number, so this affects
|
|
59
|
+
* the displayed flag/name on parse, not what is persisted.
|
|
60
|
+
*/
|
|
61
|
+
var PRIMARY_BY_DIAL = {
|
|
62
|
+
'1': 'US', '7': 'RU', '39': 'IT', '44': 'GB', '47': 'NO', '61': 'AU',
|
|
63
|
+
'64': 'NZ', '212': 'MA', '262': 'RE', '290': 'SH', '358': 'FI',
|
|
64
|
+
'500': 'FK', '590': 'GP', '599': 'CW',
|
|
65
|
+
};
|
|
66
|
+
/** Calling code (digits) → representative ISO country code. */
|
|
67
|
+
export var ISO_BY_DIAL = (function () {
|
|
68
|
+
var map = {};
|
|
69
|
+
for (var _i = 0, _a = Object.entries(DIAL_BY_ISO); _i < _a.length; _i++) {
|
|
70
|
+
var _b = _a[_i], iso = _b[0], code = _b[1];
|
|
71
|
+
if (!(code in map))
|
|
72
|
+
map[code] = iso; // first-wins for unique codes
|
|
73
|
+
}
|
|
74
|
+
Object.assign(map, PRIMARY_BY_DIAL); // explicit winners for shared codes
|
|
75
|
+
return map;
|
|
76
|
+
})();
|
|
77
|
+
/**
|
|
78
|
+
* North American Numbering Plan: +1 is shared by ~25 countries, but each
|
|
79
|
+
* 3-digit area code (NPA) belongs to exactly one. Maps the non-US NPAs to their
|
|
80
|
+
* country; any NPA not listed (and short/partial numbers) falls back to the +1
|
|
81
|
+
* representative (US). This lets a stored +1 number resolve back to, e.g.,
|
|
82
|
+
* Canada rather than always showing the US.
|
|
83
|
+
*/
|
|
84
|
+
var NANP_BY_NPA = (function () {
|
|
85
|
+
var map = {
|
|
86
|
+
'242': 'BS', '246': 'BB', '264': 'AI', '268': 'AG', '284': 'VG',
|
|
87
|
+
'340': 'VI', '345': 'KY', '441': 'BM', '473': 'GD', '649': 'TC',
|
|
88
|
+
'658': 'JM', '664': 'MS', '670': 'MP', '671': 'GU', '684': 'AS',
|
|
89
|
+
'721': 'SX', '758': 'LC', '767': 'DM', '784': 'VC', '787': 'PR',
|
|
90
|
+
'809': 'DO', '829': 'DO', '849': 'DO', '868': 'TT', '869': 'KN',
|
|
91
|
+
'876': 'JM', '939': 'PR',
|
|
92
|
+
};
|
|
93
|
+
var caNpas = [
|
|
94
|
+
'204', '226', '236', '249', '250', '263', '289', '306', '343', '354',
|
|
95
|
+
'365', '367', '368', '382', '403', '416', '418', '428', '431', '437',
|
|
96
|
+
'438', '450', '468', '474', '506', '514', '519', '548', '579', '581',
|
|
97
|
+
'584', '587', '604', '613', '639', '647', '672', '683', '705', '709',
|
|
98
|
+
'742', '753', '778', '780', '782', '807', '819', '825', '867', '873',
|
|
99
|
+
'879', '902', '905',
|
|
100
|
+
];
|
|
101
|
+
for (var _i = 0, caNpas_1 = caNpas; _i < caNpas_1.length; _i++) {
|
|
102
|
+
var npa = caNpas_1[_i];
|
|
103
|
+
map[npa] = 'CA';
|
|
104
|
+
}
|
|
105
|
+
return map;
|
|
106
|
+
})();
|
|
107
|
+
export var digitsOnly = function (s) { return (s || '').replace(/\D/g, ''); };
|
|
108
|
+
/**
|
|
109
|
+
* Split an E.164 string ("+351123456789") into its calling code and national
|
|
110
|
+
* part. Relies on calling codes being prefix-free, so the first prefix (of
|
|
111
|
+
* length 1–3) that is a known code is the unique correct one.
|
|
112
|
+
*/
|
|
113
|
+
export var parseE164 = function (value) {
|
|
114
|
+
var _a;
|
|
115
|
+
var cleaned = (value || '').replace(/[^\d+]/g, '');
|
|
116
|
+
if (!cleaned.startsWith('+'))
|
|
117
|
+
return { iso: null, code: null, national: '' };
|
|
118
|
+
var rest = cleaned.slice(1);
|
|
119
|
+
for (var len = 1; len <= 3; len++) {
|
|
120
|
+
var code = rest.slice(0, len);
|
|
121
|
+
if (ISO_BY_DIAL[code]) {
|
|
122
|
+
var iso = ISO_BY_DIAL[code];
|
|
123
|
+
// +1 is shared across the NANP; resolve the specific country from the
|
|
124
|
+
// area code (the 3 digits after the country code).
|
|
125
|
+
if (code === '1') {
|
|
126
|
+
iso = (_a = NANP_BY_NPA[rest.slice(len, len + 3)]) !== null && _a !== void 0 ? _a : iso;
|
|
127
|
+
}
|
|
128
|
+
return { iso: iso, code: code, national: rest.slice(len) };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { iso: null, code: null, national: rest };
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Build an E.164 string from a selected country and a national part. Returns
|
|
135
|
+
* '' when there is no national number, so the field stays empty/optional
|
|
136
|
+
* rather than persisting a bare "+44".
|
|
137
|
+
*/
|
|
138
|
+
export var assembleE164 = function (iso, national) {
|
|
139
|
+
var code = DIAL_BY_ISO[iso];
|
|
140
|
+
var digits = digitsOnly(national);
|
|
141
|
+
return code && digits ? "+".concat(code).concat(digits) : '';
|
|
142
|
+
};
|
package/inputs/Editor/Editor.js
CHANGED
|
@@ -78,10 +78,16 @@ var EditorBase = function (props) {
|
|
|
78
78
|
var wrapperRef = React.useRef(null);
|
|
79
79
|
// Ref to actual editor div (with contentEditable).
|
|
80
80
|
var contentEditableRef = React.useRef(null);
|
|
81
|
+
// Last HTML the editor itself emitted via onUpdate. Used to detect
|
|
82
|
+
// round-trips so we don't re-sanitize the editor's own output (which would
|
|
83
|
+
// strip the empty <li> created by pressing Enter on a list item).
|
|
84
|
+
var lastEmittedRef = React.useRef(null);
|
|
81
85
|
var _a = React.useState(false), fullscreen = _a[0], setFullscreen = _a[1];
|
|
82
86
|
var handleUpdate = function (p) {
|
|
87
|
+
var html = p.editor.getHTML();
|
|
88
|
+
lastEmittedRef.current = html;
|
|
83
89
|
if (props.onChange)
|
|
84
|
-
props.onChange(
|
|
90
|
+
props.onChange(html);
|
|
85
91
|
};
|
|
86
92
|
var handleFullscreenChange = function () {
|
|
87
93
|
setFullscreen(document.fullscreenElement != null);
|
|
@@ -105,10 +111,13 @@ var EditorBase = function (props) {
|
|
|
105
111
|
onUpdate: handleUpdate
|
|
106
112
|
});
|
|
107
113
|
React.useEffect(function () {
|
|
114
|
+
// If props.value is exactly what the editor just emitted, this is a
|
|
115
|
+
// round-trip from our own onChange — skip sanitizing and re-setting
|
|
116
|
+
// content. Sanitizing here would, e.g., strip the empty <li> created by
|
|
117
|
+
// pressing Enter inside a list, undoing the user's keystroke.
|
|
118
|
+
if (props.value === lastEmittedRef.current)
|
|
119
|
+
return;
|
|
108
120
|
var safeValue = sanitizeEditorHTML(props.value);
|
|
109
|
-
// Only replace content if it actually differs from what's in the editor.
|
|
110
|
-
// This avoids resetting the cursor when the editor's own changes round-trip
|
|
111
|
-
// back through props.
|
|
112
121
|
if (editor.getHTML() !== safeValue) {
|
|
113
122
|
editor.commands.setContent(safeValue, true, { preserveWhitespace: "full" });
|
|
114
123
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ITestable } from '../../Types';
|
|
3
|
+
interface IPhoneInputProps extends ITestable {
|
|
4
|
+
/** @ignore */
|
|
5
|
+
className?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Field name, for use in forms.
|
|
8
|
+
*/
|
|
9
|
+
name?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Current value in E.164 format, e.g. `"+351123456789"`.
|
|
12
|
+
*/
|
|
13
|
+
value?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Notified with the assembled E.164 string whenever the country or number
|
|
16
|
+
* changes. Receives `''` when the field is cleared.
|
|
17
|
+
*/
|
|
18
|
+
onChange?: (value: string) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Country selected for new/empty values, and the country restored when the
|
|
21
|
+
* field is cleared (ISO 3166-1 alpha-2, e.g. `"GB"`). A country is always
|
|
22
|
+
* selected, which is why there is no country placeholder.
|
|
23
|
+
* @default US
|
|
24
|
+
*/
|
|
25
|
+
defaultCountry?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Placeholder shown in the phone-number field when it is empty. The country
|
|
28
|
+
* field needs no placeholder, as a country is always selected.
|
|
29
|
+
*/
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
/**
|
|
32
|
+
* If set, the phone-number field shows a clear button that empties the
|
|
33
|
+
* number and resets the country to the default.
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
clearable?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Marks the control as disabled. Disabled controls cannot be interacted with.
|
|
39
|
+
* @default false
|
|
40
|
+
*/
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Shows the control in an error state.
|
|
44
|
+
* @default false
|
|
45
|
+
*/
|
|
46
|
+
error?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* A fluid control takes up all horizontal space available to it.
|
|
49
|
+
* @default false
|
|
50
|
+
*/
|
|
51
|
+
fluid?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Removes borders and makes the background transparent. Useful when placing
|
|
54
|
+
* the control inside a `Field`.
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
transparent?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Shows the control in a ghost state, where content is being loaded.
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
ghost?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Locale code for country-name display and sorting (e.g. `"pt"`, `"es"`).
|
|
65
|
+
* @default en
|
|
66
|
+
*/
|
|
67
|
+
locale?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A phone-number input: a searchable country dropdown that supplies the
|
|
71
|
+
* international dialing prefix, beside a free-text field for the rest of the
|
|
72
|
+
* number. The value is a single E.164 string (`"+351123456789"`) — the prefix
|
|
73
|
+
* is never stored separately.
|
|
74
|
+
*
|
|
75
|
+
* A country is always selected (default US, configurable via `defaultCountry`),
|
|
76
|
+
* so there is no country placeholder to translate; the `placeholder` prop
|
|
77
|
+
* applies to the number field. It satisfies the `Form.Field` control contract (`value` +
|
|
78
|
+
* `onChange(value)`), so a surrounding `Form.Field` validates the assembled
|
|
79
|
+
* E.164 string.
|
|
80
|
+
*
|
|
81
|
+
* ## Usage
|
|
82
|
+
*
|
|
83
|
+
* ```tsx
|
|
84
|
+
* <PhoneInput value="+351123456789" onChange={setPhone} clearable/>
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
declare const PhoneInput: {
|
|
88
|
+
(props: IPhoneInputProps): React.JSX.Element;
|
|
89
|
+
displayName: string;
|
|
90
|
+
};
|
|
91
|
+
export { PhoneInput };
|
|
92
|
+
export type { IPhoneInputProps };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
|
|
2
|
+
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
|
|
3
|
+
return cooked;
|
|
4
|
+
};
|
|
5
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
6
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
7
|
+
if (ar || !(i in from)) {
|
|
8
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
9
|
+
ar[i] = from[i];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
13
|
+
};
|
|
14
|
+
import * as React from 'react';
|
|
15
|
+
import styled from 'styled-components';
|
|
16
|
+
import { Dropdown } from '../Dropdown';
|
|
17
|
+
import { Input } from '../Input';
|
|
18
|
+
import { CountryFormatter } from '../../formatters/CountryFormatter';
|
|
19
|
+
import { Countries } from '../../formatters/CountryFormatter/Countries';
|
|
20
|
+
import { DIAL_BY_ISO, parseE164, assembleE164, digitsOnly } from '../../helper/phoneNumber';
|
|
21
|
+
var ALL_ISO = Object.keys(DIAL_BY_ISO);
|
|
22
|
+
var nameOf = function (iso) { var _a; return (_a = Countries[iso]) !== null && _a !== void 0 ? _a : iso; };
|
|
23
|
+
var strip = function (s) { return s.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase(); };
|
|
24
|
+
/**
|
|
25
|
+
* A phone-number input: a searchable country dropdown that supplies the
|
|
26
|
+
* international dialing prefix, beside a free-text field for the rest of the
|
|
27
|
+
* number. The value is a single E.164 string (`"+351123456789"`) — the prefix
|
|
28
|
+
* is never stored separately.
|
|
29
|
+
*
|
|
30
|
+
* A country is always selected (default US, configurable via `defaultCountry`),
|
|
31
|
+
* so there is no country placeholder to translate; the `placeholder` prop
|
|
32
|
+
* applies to the number field. It satisfies the `Form.Field` control contract (`value` +
|
|
33
|
+
* `onChange(value)`), so a surrounding `Form.Field` validates the assembled
|
|
34
|
+
* E.164 string.
|
|
35
|
+
*
|
|
36
|
+
* ## Usage
|
|
37
|
+
*
|
|
38
|
+
* ```tsx
|
|
39
|
+
* <PhoneInput value="+351123456789" onChange={setPhone} clearable/>
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
var PhoneInput = function (props) {
|
|
43
|
+
var value = props.value, onChange = props.onChange, _a = props.defaultCountry, defaultCountry = _a === void 0 ? 'US' : _a, clearable = props.clearable, locale = props.locale, placeholder = props.placeholder, disabled = props.disabled, error = props.error, fluid = props.fluid, transparent = props.transparent, ghost = props.ghost, className = props.className, name = props.name;
|
|
44
|
+
var _b = React.useState(function () { var _a; return (_a = parseE164(value).iso) !== null && _a !== void 0 ? _a : defaultCountry; }), iso = _b[0], setIso = _b[1];
|
|
45
|
+
var _c = React.useState(function () { return parseE164(value).national; }), national = _c[0], setNational = _c[1];
|
|
46
|
+
var _d = React.useState(''), query = _d[0], setQuery = _d[1];
|
|
47
|
+
// Re-sync from an externally-set value (initial load, form reset). During
|
|
48
|
+
// normal typing the parent value matches our assembled output, so this no-ops
|
|
49
|
+
// and won't fight the user.
|
|
50
|
+
React.useEffect(function () {
|
|
51
|
+
if ((value !== null && value !== void 0 ? value : '') !== assembleE164(iso, national)) {
|
|
52
|
+
var parsed = parseE164(value);
|
|
53
|
+
if (parsed.iso)
|
|
54
|
+
setIso(parsed.iso);
|
|
55
|
+
setNational(parsed.national);
|
|
56
|
+
}
|
|
57
|
+
}, [value]);
|
|
58
|
+
var sortedIso = React.useMemo(function () { return __spreadArray([], ALL_ISO, true).sort(function (a, b) { return nameOf(a).localeCompare(nameOf(b)); }); }, []);
|
|
59
|
+
var data = React.useMemo(function () {
|
|
60
|
+
var q = strip(query);
|
|
61
|
+
if (!q)
|
|
62
|
+
return sortedIso;
|
|
63
|
+
var qDigits = digitsOnly(query);
|
|
64
|
+
return sortedIso.filter(function (c) { return strip(nameOf(c)).includes(q) || (qDigits !== '' && DIAL_BY_ISO[c].startsWith(qDigits)); });
|
|
65
|
+
}, [query, sortedIso]);
|
|
66
|
+
var handleCountry = function (next) {
|
|
67
|
+
var nextIso = next !== null && next !== void 0 ? next : defaultCountry;
|
|
68
|
+
setIso(nextIso);
|
|
69
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(assembleE164(nextIso, national));
|
|
70
|
+
};
|
|
71
|
+
var handleNational = function (next) {
|
|
72
|
+
// The Input's clear button emits null (typing only ever emits a string).
|
|
73
|
+
// Treat it as "clear the whole control": empty the number and reset the
|
|
74
|
+
// country to the default.
|
|
75
|
+
if (next === null) {
|
|
76
|
+
setIso(defaultCountry);
|
|
77
|
+
setNational('');
|
|
78
|
+
onChange === null || onChange === void 0 ? void 0 : onChange('');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
var digits = digitsOnly(next);
|
|
82
|
+
setNational(digits);
|
|
83
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(assembleE164(iso, digits));
|
|
84
|
+
};
|
|
85
|
+
return (
|
|
86
|
+
// Stop clicks bubbling to the Form.Field box wrapper. That wrapper routes
|
|
87
|
+
// any click to the first child with tabIndex=0 (the Dropdown), which would
|
|
88
|
+
// otherwise hijack clicks on the number field and force the dropdown open.
|
|
89
|
+
React.createElement(Wrapper, { "$fluid": fluid, className: className, "data-testid": props['data-testid'], onClick: function (e) { return e.stopPropagation(); } },
|
|
90
|
+
React.createElement(Prefix, null,
|
|
91
|
+
React.createElement(Dropdown, { data: data, value: iso, onChange: handleCountry, onSearch: setQuery, disabled: disabled, error: error, ghost: ghost, transparent: transparent, fluid: true, maxItems: 8, label: function (c) { return (React.createElement(Selected, null,
|
|
92
|
+
React.createElement(SelectedCountry, null,
|
|
93
|
+
React.createElement(CountryFormatter, { type: "both", value: c, locale: locale })),
|
|
94
|
+
React.createElement(Code, null,
|
|
95
|
+
"+",
|
|
96
|
+
DIAL_BY_ISO[c]))); } },
|
|
97
|
+
React.createElement(Dropdown.Column, null, function (c) { return (React.createElement(CountryFormatter, { type: "both", value: c, locale: locale })); }),
|
|
98
|
+
React.createElement(Dropdown.Column, { width: "40px", align: "right" }, function (c) { return (React.createElement(Code, null,
|
|
99
|
+
"+",
|
|
100
|
+
DIAL_BY_ISO[c])); }))),
|
|
101
|
+
React.createElement(Number, null,
|
|
102
|
+
React.createElement(Input, { name: name, value: national, onChange: handleNational, disabled: disabled, error: error, ghost: ghost, transparent: transparent, fluid: true, clearable: clearable, type: "text", autocomplete: "off", placeholder: placeholder }))));
|
|
103
|
+
};
|
|
104
|
+
PhoneInput.displayName = 'PhoneInput';
|
|
105
|
+
var Wrapper = styled.div(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 8px;\n width: ", ";\n"], ["\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 8px;\n width: ", ";\n"])), function (p) { return (p.$fluid ? '100%' : 'auto'); });
|
|
106
|
+
var Prefix = styled.div(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n flex: 0 0 auto;\n width: 220px;\n"], ["\n flex: 0 0 auto;\n width: 220px;\n"])));
|
|
107
|
+
var Number = styled.div(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n flex: 1 1 auto;\n min-width: 0;\n"], ["\n flex: 1 1 auto;\n min-width: 0;\n"])));
|
|
108
|
+
var Selected = styled.div(templateObject_4 || (templateObject_4 = __makeTemplateObject(["\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 6px;\n min-width: 0;\n white-space: nowrap;\n"], ["\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 6px;\n min-width: 0;\n white-space: nowrap;\n"])));
|
|
109
|
+
/* Lets the country name ellipsize so the dialing code stays visible. */
|
|
110
|
+
var SelectedCountry = styled.div(templateObject_5 || (templateObject_5 = __makeTemplateObject(["\n min-width: 0;\n overflow: hidden;\n"], ["\n min-width: 0;\n overflow: hidden;\n"])));
|
|
111
|
+
var Code = styled.span(templateObject_6 || (templateObject_6 = __makeTemplateObject(["\n flex: 0 0 auto;\n opacity: 0.7;\n"], ["\n flex: 0 0 auto;\n opacity: 0.7;\n"])));
|
|
112
|
+
export { PhoneInput };
|
|
113
|
+
var templateObject_1, templateObject_2, templateObject_3, templateObject_4, templateObject_5, templateObject_6;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PhoneInput } from './PhoneInput';
|