@webority-technologies/mobile-core 0.0.3 → 0.0.5
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/lib/commonjs/formatters/dateMath.js +292 -0
- package/lib/commonjs/formatters/index.js +67 -0
- package/lib/commonjs/formatters/phone.js +65 -99
- package/lib/commonjs/index.js +66 -0
- package/lib/module/formatters/dateMath.js +277 -0
- package/lib/module/formatters/index.js +1 -0
- package/lib/module/formatters/phone.js +62 -95
- package/lib/module/index.js +1 -1
- package/lib/typescript/commonjs/formatters/dateMath.d.ts +79 -0
- package/lib/typescript/commonjs/formatters/index.d.ts +1 -0
- package/lib/typescript/commonjs/formatters/phone.d.ts +28 -17
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/module/formatters/dateMath.d.ts +79 -0
- package/lib/typescript/module/formatters/index.d.ts +1 -0
- package/lib/typescript/module/formatters/phone.d.ts +28 -17
- package/lib/typescript/module/index.d.ts +1 -1
- package/package.json +1 -1
package/lib/commonjs/index.js
CHANGED
|
@@ -51,6 +51,12 @@ Object.defineProperty(exports, "VersionCheck", {
|
|
|
51
51
|
return _index8.VersionCheck;
|
|
52
52
|
}
|
|
53
53
|
});
|
|
54
|
+
Object.defineProperty(exports, "addDays", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
get: function () {
|
|
57
|
+
return _index3.addDays;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
54
60
|
Object.defineProperty(exports, "addNetworkStatusListener", {
|
|
55
61
|
enumerable: true,
|
|
56
62
|
get: function () {
|
|
@@ -87,6 +93,12 @@ Object.defineProperty(exports, "detectPhoneCountry", {
|
|
|
87
93
|
return _index3.detectPhoneCountry;
|
|
88
94
|
}
|
|
89
95
|
});
|
|
96
|
+
Object.defineProperty(exports, "diffMs", {
|
|
97
|
+
enumerable: true,
|
|
98
|
+
get: function () {
|
|
99
|
+
return _index3.diffMs;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
90
102
|
Object.defineProperty(exports, "formatCompactNumber", {
|
|
91
103
|
enumerable: true,
|
|
92
104
|
get: function () {
|
|
@@ -105,6 +117,24 @@ Object.defineProperty(exports, "formatDate", {
|
|
|
105
117
|
return _index3.formatDate;
|
|
106
118
|
}
|
|
107
119
|
});
|
|
120
|
+
Object.defineProperty(exports, "formatDateDDMMMYYYY", {
|
|
121
|
+
enumerable: true,
|
|
122
|
+
get: function () {
|
|
123
|
+
return _index3.formatDateDDMMMYYYY;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
Object.defineProperty(exports, "formatDateISO", {
|
|
127
|
+
enumerable: true,
|
|
128
|
+
get: function () {
|
|
129
|
+
return _index3.formatDateISO;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
Object.defineProperty(exports, "formatDatePattern", {
|
|
133
|
+
enumerable: true,
|
|
134
|
+
get: function () {
|
|
135
|
+
return _index3.formatDatePattern;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
108
138
|
Object.defineProperty(exports, "formatDateTime", {
|
|
109
139
|
enumerable: true,
|
|
110
140
|
get: function () {
|
|
@@ -141,6 +171,18 @@ Object.defineProperty(exports, "formatTime", {
|
|
|
141
171
|
return _index3.formatTime;
|
|
142
172
|
}
|
|
143
173
|
});
|
|
174
|
+
Object.defineProperty(exports, "formatTime12h", {
|
|
175
|
+
enumerable: true,
|
|
176
|
+
get: function () {
|
|
177
|
+
return _index3.formatTime12h;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
Object.defineProperty(exports, "formatTimeInZone", {
|
|
181
|
+
enumerable: true,
|
|
182
|
+
get: function () {
|
|
183
|
+
return _index3.formatTimeInZone;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
144
186
|
Object.defineProperty(exports, "getConfig", {
|
|
145
187
|
enumerable: true,
|
|
146
188
|
get: function () {
|
|
@@ -225,6 +267,18 @@ Object.defineProperty(exports, "isBetween", {
|
|
|
225
267
|
return _index7.isBetween;
|
|
226
268
|
}
|
|
227
269
|
});
|
|
270
|
+
Object.defineProperty(exports, "isDateAfter", {
|
|
271
|
+
enumerable: true,
|
|
272
|
+
get: function () {
|
|
273
|
+
return _index3.isDateAfter;
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
Object.defineProperty(exports, "isDateBefore", {
|
|
277
|
+
enumerable: true,
|
|
278
|
+
get: function () {
|
|
279
|
+
return _index3.isDateBefore;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
228
282
|
Object.defineProperty(exports, "isEmail", {
|
|
229
283
|
enumerable: true,
|
|
230
284
|
get: function () {
|
|
@@ -303,6 +357,12 @@ Object.defineProperty(exports, "isRetryableStatus", {
|
|
|
303
357
|
return _retry.isRetryableStatus;
|
|
304
358
|
}
|
|
305
359
|
});
|
|
360
|
+
Object.defineProperty(exports, "isSameDay", {
|
|
361
|
+
enumerable: true,
|
|
362
|
+
get: function () {
|
|
363
|
+
return _index3.isSameDay;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
306
366
|
Object.defineProperty(exports, "isStorageAvailable", {
|
|
307
367
|
enumerable: true,
|
|
308
368
|
get: function () {
|
|
@@ -435,6 +495,12 @@ Object.defineProperty(exports, "setStorageImplementation", {
|
|
|
435
495
|
return _index6.setStorageImplementation;
|
|
436
496
|
}
|
|
437
497
|
});
|
|
498
|
+
Object.defineProperty(exports, "startOfDay", {
|
|
499
|
+
enumerable: true,
|
|
500
|
+
get: function () {
|
|
501
|
+
return _index3.startOfDay;
|
|
502
|
+
}
|
|
503
|
+
});
|
|
438
504
|
Object.defineProperty(exports, "storeToken", {
|
|
439
505
|
enumerable: true,
|
|
440
506
|
get: function () {
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const toDate = value => {
|
|
4
|
+
if (value instanceof Date) {
|
|
5
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
6
|
+
}
|
|
7
|
+
const d = new Date(value);
|
|
8
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
9
|
+
};
|
|
10
|
+
const PAD = n => n < 10 ? `0${n}` : `${n}`;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Whether two values fall on the same calendar day in the device's local
|
|
14
|
+
* timezone. Invalid input on either side returns false rather than throwing.
|
|
15
|
+
*/
|
|
16
|
+
export const isSameDay = (a, b) => {
|
|
17
|
+
const da = toDate(a);
|
|
18
|
+
const db = toDate(b);
|
|
19
|
+
if (!da || !db) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return da.getFullYear() === db.getFullYear() && da.getMonth() === db.getMonth() && da.getDate() === db.getDate();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** `a` is strictly before `b`. Invalid input on either side returns false. */
|
|
26
|
+
export const isDateBefore = (a, b) => {
|
|
27
|
+
const da = toDate(a);
|
|
28
|
+
const db = toDate(b);
|
|
29
|
+
return da !== null && db !== null && da.getTime() < db.getTime();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** `a` is strictly after `b`. Invalid input on either side returns false. */
|
|
33
|
+
export const isDateAfter = (a, b) => {
|
|
34
|
+
const da = toDate(a);
|
|
35
|
+
const db = toDate(b);
|
|
36
|
+
return da !== null && db !== null && da.getTime() > db.getTime();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* `a - b` in milliseconds, matching dayjs's `a.diff(b)` sign convention
|
|
41
|
+
* (positive when `a` is later than `b`). Returns `NaN` if either side fails
|
|
42
|
+
* to parse, the same way subtracting two invalid dayjs objects would.
|
|
43
|
+
*/
|
|
44
|
+
export const diffMs = (a, b) => {
|
|
45
|
+
const da = toDate(a);
|
|
46
|
+
const db = toDate(b);
|
|
47
|
+
if (!da || !db) {
|
|
48
|
+
return Number.NaN;
|
|
49
|
+
}
|
|
50
|
+
return da.getTime() - db.getTime();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Midnight of the given date, in the device's local timezone. Returns null
|
|
55
|
+
* for unparseable input.
|
|
56
|
+
*/
|
|
57
|
+
export const startOfDay = value => {
|
|
58
|
+
const d = toDate(value);
|
|
59
|
+
if (!d) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const start = new Date(d);
|
|
63
|
+
start.setHours(0, 0, 0, 0);
|
|
64
|
+
return start;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Adds (or, with a negative count, subtracts) whole calendar days in the
|
|
69
|
+
* device's local timezone. Returns null for unparseable input.
|
|
70
|
+
*/
|
|
71
|
+
export const addDays = (value, count) => {
|
|
72
|
+
const d = toDate(value);
|
|
73
|
+
if (!d) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const next = new Date(d);
|
|
77
|
+
next.setDate(next.getDate() + count);
|
|
78
|
+
return next;
|
|
79
|
+
};
|
|
80
|
+
const WEEKDAY_SHORT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
81
|
+
const WEEKDAY_LONG = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
82
|
+
const MONTH_LONG = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
83
|
+
|
|
84
|
+
// Longest-first so 'YYYY' is tried before 'YY', 'DD' before 'D', etc. — a
|
|
85
|
+
// greedy single pass over the pattern, not a regex, so a literal character
|
|
86
|
+
// (a comma, a colon, a digit) can never accidentally match a token.
|
|
87
|
+
const TOKENS = ['YYYY', 'YY', 'MMMM', 'MMM', 'MM', 'M', 'DD', 'D', 'dddd', 'ddd', 'HH', 'H', 'hh', 'h', 'mm', 'm', 'ss', 's', 'A', 'a'];
|
|
88
|
+
const tokenizePattern = pattern => {
|
|
89
|
+
const result = [];
|
|
90
|
+
let i = 0;
|
|
91
|
+
outer: while (i < pattern.length) {
|
|
92
|
+
for (const token of TOKENS) {
|
|
93
|
+
if (pattern.startsWith(token, i)) {
|
|
94
|
+
result.push(token);
|
|
95
|
+
i += token.length;
|
|
96
|
+
continue outer;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
result.push(pattern[i]);
|
|
100
|
+
i += 1;
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Small dayjs/moment-style token formatter — YYYY/YY, MMMM/MMM/MM/M,
|
|
107
|
+
* DD/D, dddd/ddd, HH/H (24h), hh/h (12h), mm/m, ss/s, A/a (upper/lower AM/PM).
|
|
108
|
+
* Everything else in the pattern (spaces, commas, colons) passes through
|
|
109
|
+
* literally. Hand-rolled rather than a dependency because this fleet's date
|
|
110
|
+
* formatting needs are a small, closed token set, not general i18n — for
|
|
111
|
+
* genuinely locale-aware output, `formatDate`/`formatTime` (Intl-backed, in
|
|
112
|
+
* `./date`) are the ones to reach for; this is for matching an exact
|
|
113
|
+
* existing pattern (a legacy display string, an API's expected shape).
|
|
114
|
+
*
|
|
115
|
+
* `utc: true` reads every field from the UTC calendar/clock instead of the
|
|
116
|
+
* device's local one — see `formatDateDDMMMYYYY`'s note on why this isn't
|
|
117
|
+
* cosmetic.
|
|
118
|
+
*/
|
|
119
|
+
export const formatDatePattern = (value, pattern, options) => {
|
|
120
|
+
const d = toDate(value);
|
|
121
|
+
if (!d) {
|
|
122
|
+
return '';
|
|
123
|
+
}
|
|
124
|
+
const utc = options?.utc ?? false;
|
|
125
|
+
const year = utc ? d.getUTCFullYear() : d.getFullYear();
|
|
126
|
+
const month = utc ? d.getUTCMonth() : d.getMonth();
|
|
127
|
+
const day = utc ? d.getUTCDate() : d.getDate();
|
|
128
|
+
const weekday = utc ? d.getUTCDay() : d.getDay();
|
|
129
|
+
const hours24 = utc ? d.getUTCHours() : d.getHours();
|
|
130
|
+
const minutes = utc ? d.getUTCMinutes() : d.getMinutes();
|
|
131
|
+
const seconds = utc ? d.getUTCSeconds() : d.getSeconds();
|
|
132
|
+
const {
|
|
133
|
+
hour12,
|
|
134
|
+
period
|
|
135
|
+
} = to12Hour(hours24);
|
|
136
|
+
return tokenizePattern(pattern).map(t => {
|
|
137
|
+
switch (t) {
|
|
138
|
+
case 'YYYY':
|
|
139
|
+
return String(year);
|
|
140
|
+
case 'YY':
|
|
141
|
+
return String(year).slice(-2);
|
|
142
|
+
case 'MMMM':
|
|
143
|
+
return MONTH_LONG[month];
|
|
144
|
+
case 'MMM':
|
|
145
|
+
return MONTH_SHORT[month];
|
|
146
|
+
case 'MM':
|
|
147
|
+
return PAD(month + 1);
|
|
148
|
+
case 'M':
|
|
149
|
+
return String(month + 1);
|
|
150
|
+
case 'DD':
|
|
151
|
+
return PAD(day);
|
|
152
|
+
case 'D':
|
|
153
|
+
return String(day);
|
|
154
|
+
case 'dddd':
|
|
155
|
+
return WEEKDAY_LONG[weekday];
|
|
156
|
+
case 'ddd':
|
|
157
|
+
return WEEKDAY_SHORT[weekday];
|
|
158
|
+
case 'HH':
|
|
159
|
+
return PAD(hours24);
|
|
160
|
+
case 'H':
|
|
161
|
+
return String(hours24);
|
|
162
|
+
case 'hh':
|
|
163
|
+
return PAD(hour12);
|
|
164
|
+
case 'h':
|
|
165
|
+
return String(hour12);
|
|
166
|
+
case 'mm':
|
|
167
|
+
return PAD(minutes);
|
|
168
|
+
case 'm':
|
|
169
|
+
return String(minutes);
|
|
170
|
+
case 'ss':
|
|
171
|
+
return PAD(seconds);
|
|
172
|
+
case 's':
|
|
173
|
+
return String(seconds);
|
|
174
|
+
case 'A':
|
|
175
|
+
return period;
|
|
176
|
+
case 'a':
|
|
177
|
+
return period.toLowerCase();
|
|
178
|
+
default:
|
|
179
|
+
return t;
|
|
180
|
+
}
|
|
181
|
+
}).join('');
|
|
182
|
+
};
|
|
183
|
+
const MONTH_SHORT = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* `DD MMM, YYYY` — zero-padded day, short month, comma, year (e.g.
|
|
187
|
+
* `05 Sep, 2025`). A specific legacy pattern some apps already display,
|
|
188
|
+
* not the fleet's own product convention: `formatDate` (the Intl `style`-based
|
|
189
|
+
* formatter in `./date`) is the one to reach for in new code. This exists so
|
|
190
|
+
* an app already showing this exact pattern can move off a date library
|
|
191
|
+
* without a visible change.
|
|
192
|
+
*
|
|
193
|
+
* `utc: true` reads the calendar day from the UTC fields instead of the
|
|
194
|
+
* device's local ones — matching `dayjs.utc(x).format(...)`, not
|
|
195
|
+
* `dayjs(x).format(...)`. The two disagree whenever local time has crossed a
|
|
196
|
+
* day boundary UTC hasn't yet (or vice versa), so this is not a cosmetic
|
|
197
|
+
* option; pass it only to match a call site that was genuinely UTC-explicit.
|
|
198
|
+
*/
|
|
199
|
+
export const formatDateDDMMMYYYY = (value, options) => {
|
|
200
|
+
const d = toDate(value);
|
|
201
|
+
if (!d) {
|
|
202
|
+
return '';
|
|
203
|
+
}
|
|
204
|
+
if (options?.utc) {
|
|
205
|
+
return `${PAD(d.getUTCDate())} ${MONTH_SHORT[d.getUTCMonth()]}, ${d.getUTCFullYear()}`;
|
|
206
|
+
}
|
|
207
|
+
return `${PAD(d.getDate())} ${MONTH_SHORT[d.getMonth()]}, ${d.getFullYear()}`;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/** `YYYY-MM-DD`, in the device's local timezone. */
|
|
211
|
+
export const formatDateISO = value => {
|
|
212
|
+
const d = toDate(value);
|
|
213
|
+
if (!d) {
|
|
214
|
+
return '';
|
|
215
|
+
}
|
|
216
|
+
return `${d.getFullYear()}-${PAD(d.getMonth() + 1)}-${PAD(d.getDate())}`;
|
|
217
|
+
};
|
|
218
|
+
const to12Hour = hours => {
|
|
219
|
+
const period = hours >= 12 ? 'PM' : 'AM';
|
|
220
|
+
const hour12 = hours % 12 || 12;
|
|
221
|
+
return {
|
|
222
|
+
hour12,
|
|
223
|
+
period
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* `hh:mm A` in the device's local timezone — zero-padded 12-hour clock,
|
|
229
|
+
* uppercase AM/PM (e.g. `09:30 PM`).
|
|
230
|
+
*/
|
|
231
|
+
export const formatTime12h = value => {
|
|
232
|
+
const d = toDate(value);
|
|
233
|
+
if (!d) {
|
|
234
|
+
return '';
|
|
235
|
+
}
|
|
236
|
+
const {
|
|
237
|
+
hour12,
|
|
238
|
+
period
|
|
239
|
+
} = to12Hour(d.getHours());
|
|
240
|
+
return `${PAD(hour12)}:${PAD(d.getMinutes())} ${period}`;
|
|
241
|
+
};
|
|
242
|
+
const hasIntl = () => typeof Intl !== 'undefined' && typeof Intl.DateTimeFormat === 'function';
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* `hh:mm a` (zero-padded 12-hour, lowercase am/pm) of the given instant as
|
|
246
|
+
* seen in `ianaTimeZone` (e.g. `Asia/Kolkata`) — not the device's own
|
|
247
|
+
* timezone. Reads the hour/minute/period through `Intl.DateTimeFormat`'s
|
|
248
|
+
* `formatToParts` rather than its assembled string, because the assembled
|
|
249
|
+
* string's spacing before AM/PM varies by JS engine (some insert a narrow
|
|
250
|
+
* no-break space, U+202F, instead of a plain space) and isn't reliably
|
|
251
|
+
* zero-padded either. Throws if `ianaTimeZone` isn't a timezone Intl
|
|
252
|
+
* recognises — the same failure mode as handing dayjs.tz() a bad zone name.
|
|
253
|
+
*/
|
|
254
|
+
export const formatTimeInZone = (value, ianaTimeZone) => {
|
|
255
|
+
const d = toDate(value);
|
|
256
|
+
if (!d) {
|
|
257
|
+
return '';
|
|
258
|
+
}
|
|
259
|
+
if (!hasIntl()) {
|
|
260
|
+
// No Intl.DateTimeFormat means no reliable IANA timezone database to
|
|
261
|
+
// convert against; the device-local fallback below is the least-wrong
|
|
262
|
+
// answer available, not a genuine timezone conversion.
|
|
263
|
+
return formatTime12h(d);
|
|
264
|
+
}
|
|
265
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
|
266
|
+
timeZone: ianaTimeZone,
|
|
267
|
+
hour: 'numeric',
|
|
268
|
+
minute: '2-digit',
|
|
269
|
+
hour12: true
|
|
270
|
+
}).formatToParts(d);
|
|
271
|
+
const get = type => parts.find(p => p.type === type)?.value ?? '';
|
|
272
|
+
const hour = Number.parseInt(get('hour'), 10);
|
|
273
|
+
const minute = get('minute');
|
|
274
|
+
const period = get('dayPeriod').toLowerCase();
|
|
275
|
+
return `${PAD(hour)}:${minute} ${period}`;
|
|
276
|
+
};
|
|
277
|
+
//# sourceMappingURL=dateMath.js.map
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
export { formatCurrency } from "./currency.js";
|
|
4
4
|
export { formatDate, formatDateTime, formatRelativeTime, formatTime } from "./date.js";
|
|
5
|
+
export { addDays, diffMs, formatDateDDMMMYYYY, formatDateISO, formatDatePattern, formatTime12h, formatTimeInZone, isDateAfter, isDateBefore, isSameDay, startOfDay } from "./dateMath.js";
|
|
5
6
|
export { getInitials } from "./initials.js";
|
|
6
7
|
export { formatCompactNumber, formatNumber, formatPercent } from "./number.js";
|
|
7
8
|
export { detectPhoneCountry, formatPhone, isValidPhoneNumber, normalizePhone, parsePhoneNumber, parsePhoneNumberFromString } from "./phone.js";
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
* The bare 'libphonenumber-js' entry point actually resolves to the library's
|
|
5
|
+
* OWN slimmed-down min/ metadata internally, not its full data - './max' is
|
|
6
|
+
* the variant with complete per-country metadata. Since the reason this
|
|
7
|
+
* module exists is validation and formatting accuracy, the more accurate
|
|
8
|
+
* variant wins throughout this file, not just for validation.
|
|
9
|
+
*/
|
|
10
|
+
import { parsePhoneNumberFromString } from 'libphonenumber-js/max';
|
|
11
|
+
export { isValidPhoneNumber, parsePhoneNumber, parsePhoneNumberFromString } from 'libphonenumber-js/max';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Re-exported under our own name rather than importing CountryCode directly
|
|
15
|
+
* everywhere, so a future need to diverge (or re-narrow) has one place to do
|
|
16
|
+
* it. Every ISO 3166-1 alpha-2 code libphonenumber-js recognises - formatPhone
|
|
17
|
+
* and detectPhoneCountry below are genuinely correct for all of them, not a
|
|
18
|
+
* curated subset.
|
|
11
19
|
*/
|
|
12
20
|
|
|
13
|
-
export { isValidPhoneNumber, parsePhoneNumber, parsePhoneNumberFromString } from 'libphonenumber-js';
|
|
14
21
|
/**
|
|
15
22
|
* Strip everything except digits and a leading `+` (preserved if present).
|
|
16
23
|
*/
|
|
@@ -23,62 +30,11 @@ export const normalizePhone = input => {
|
|
|
23
30
|
const digits = trimmed.replace(/\D/g, '');
|
|
24
31
|
return hasPlus ? `+${digits}` : digits;
|
|
25
32
|
};
|
|
26
|
-
const groupIndianMobile = digits => {
|
|
27
|
-
// Indian mobile is 10 digits: format as `XXXXX XXXXX`.
|
|
28
|
-
if (digits.length !== 10) {
|
|
29
|
-
return digits;
|
|
30
|
-
}
|
|
31
|
-
return `${digits.slice(0, 5)} ${digits.slice(5)}`;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// Longest calling code first so a 3-digit code is never shadowed by a shorter one.
|
|
35
|
-
const COUNTRY_RULES = [{
|
|
36
|
-
country: 'AE',
|
|
37
|
-
callingCode: '971',
|
|
38
|
-
nationalLength: 9,
|
|
39
|
-
group: n => `${n.slice(0, 2)} ${n.slice(2, 5)} ${n.slice(5)}`
|
|
40
|
-
}, {
|
|
41
|
-
country: 'IN',
|
|
42
|
-
callingCode: '91',
|
|
43
|
-
nationalLength: 10,
|
|
44
|
-
group: groupIndianMobile
|
|
45
|
-
}, {
|
|
46
|
-
country: 'GB',
|
|
47
|
-
callingCode: '44',
|
|
48
|
-
nationalLength: 10,
|
|
49
|
-
group: n => `${n.slice(0, 4)} ${n.slice(4, 7)} ${n.slice(7)}`
|
|
50
|
-
}, {
|
|
51
|
-
country: 'AU',
|
|
52
|
-
callingCode: '61',
|
|
53
|
-
nationalLength: 9,
|
|
54
|
-
group: n => `${n.slice(0, 3)} ${n.slice(3, 6)} ${n.slice(6)}`
|
|
55
|
-
}, {
|
|
56
|
-
country: 'SG',
|
|
57
|
-
callingCode: '65',
|
|
58
|
-
nationalLength: 8,
|
|
59
|
-
group: n => `${n.slice(0, 4)} ${n.slice(4)}`
|
|
60
|
-
}, {
|
|
61
|
-
country: 'US',
|
|
62
|
-
callingCode: '1',
|
|
63
|
-
nationalLength: 10,
|
|
64
|
-
group: n => `(${n.slice(0, 3)}) ${n.slice(3, 6)}-${n.slice(6)}`
|
|
65
|
-
},
|
|
66
|
-
// US and CA share a calling code and are indistinguishable from it alone;
|
|
67
|
-
// this row only serves an explicit `country: 'CA'` override, never detection
|
|
68
|
-
// (it sits after the US row, and findRuleByCallingCode returns the first match).
|
|
69
|
-
{
|
|
70
|
-
country: 'CA',
|
|
71
|
-
callingCode: '1',
|
|
72
|
-
nationalLength: 10,
|
|
73
|
-
group: n => `(${n.slice(0, 3)}) ${n.slice(3, 6)}-${n.slice(6)}`
|
|
74
|
-
}];
|
|
75
|
-
const findRuleByCallingCode = rest => COUNTRY_RULES.find(rule => rest.startsWith(rule.callingCode));
|
|
76
|
-
const findRuleByCountry = country => COUNTRY_RULES.find(rule => rule.country === country);
|
|
77
33
|
|
|
78
34
|
/**
|
|
79
35
|
* Detect the country of a phone number: from its own `+` country code when
|
|
80
36
|
* present, otherwise from `defaultCountryCode`. Returns null when neither
|
|
81
|
-
*
|
|
37
|
+
* parses to a recognised country.
|
|
82
38
|
*/
|
|
83
39
|
export const detectPhoneCountry = (input, defaultCountryCode = '+91') => {
|
|
84
40
|
const normalized = normalizePhone(input);
|
|
@@ -86,17 +42,18 @@ export const detectPhoneCountry = (input, defaultCountryCode = '+91') => {
|
|
|
86
42
|
return null;
|
|
87
43
|
}
|
|
88
44
|
if (normalized.startsWith('+')) {
|
|
89
|
-
return
|
|
45
|
+
return parsePhoneNumberFromString(normalized)?.country ?? null;
|
|
90
46
|
}
|
|
91
47
|
const normalizedDefault = normalizePhone(defaultCountryCode);
|
|
92
48
|
if (!normalizedDefault.startsWith('+')) {
|
|
93
49
|
return null;
|
|
94
50
|
}
|
|
95
|
-
return
|
|
51
|
+
return parsePhoneNumberFromString(normalizedDefault + normalized)?.country ?? null;
|
|
96
52
|
};
|
|
97
53
|
const bestEffortGroup = rest => {
|
|
98
|
-
//
|
|
99
|
-
// the country code (first 1-3
|
|
54
|
+
// Not a parseable number at all (garbage input, or a real number that's an
|
|
55
|
+
// unsupported length for its country): split the country code (first 1-3
|
|
56
|
+
// digits) and group the rest in 4s, rather than returning nothing.
|
|
100
57
|
const ccLen = rest.length > 11 ? 3 : rest.length > 10 ? 2 : 1;
|
|
101
58
|
const cc = rest.slice(0, ccLen);
|
|
102
59
|
const number = rest.slice(ccLen);
|
|
@@ -105,13 +62,18 @@ const bestEffortGroup = rest => {
|
|
|
105
62
|
};
|
|
106
63
|
|
|
107
64
|
/**
|
|
108
|
-
* Format a phone number for human display
|
|
65
|
+
* Format a phone number for human display, via libphonenumber-js's own
|
|
66
|
+
* per-country formatting rather than a hand-rolled grouping table - correct
|
|
67
|
+
* for every country it recognises, not a curated subset.
|
|
109
68
|
*
|
|
110
|
-
* -
|
|
111
|
-
* -
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
69
|
+
* - A number that already carries a `+` country code formats for that country.
|
|
70
|
+
* - A bare national number formats for `options.country` when given, else for
|
|
71
|
+
* the country implied by `defaultCountryCode` (default `+91`).
|
|
72
|
+
* - `includeCountryCode` controls international vs national format; defaults
|
|
73
|
+
* to international whenever the country was detected rather than passed in
|
|
74
|
+
* explicitly as `options.country`, and to national when it was.
|
|
75
|
+
* - Input libphonenumber-js can't parse into a valid number for the resolved
|
|
76
|
+
* country falls back to a best-effort `+CC NNNN NNNN…` digit grouping.
|
|
115
77
|
*/
|
|
116
78
|
|
|
117
79
|
export function formatPhone(input, arg) {
|
|
@@ -124,35 +86,40 @@ export function formatPhone(input, arg) {
|
|
|
124
86
|
}
|
|
125
87
|
const options = typeof arg === 'string' ? {} : arg ?? {};
|
|
126
88
|
const defaultCountryCode = typeof arg === 'string' ? arg : options.defaultCountryCode ?? '+91';
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const body = activeRule.group(national);
|
|
136
|
-
return includeCc ? `+${activeRule.callingCode} ${body}` : body;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return bestEffortGroup(rest);
|
|
140
|
-
}
|
|
89
|
+
const hasOwnCountryCode = normalized.startsWith('+');
|
|
90
|
+
let parsed;
|
|
91
|
+
// What to best-effort group if parsing fails to produce a valid number.
|
|
92
|
+
// Defaults to the plain input; the defaultCountryCode branch below points
|
|
93
|
+
// it at the combined digits instead, so a bare number that turns out
|
|
94
|
+
// invalid for its detected country still shows that country's code rather
|
|
95
|
+
// than silently discarding it.
|
|
96
|
+
let fallbackRest = normalized;
|
|
141
97
|
if (options.country) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
98
|
+
// parsePhoneNumberFromString ignores this hint when normalized already
|
|
99
|
+
// carries its own +, so a real E.164 number is never overridden by a
|
|
100
|
+
// wrong or stale options.country.
|
|
101
|
+
parsed = parsePhoneNumberFromString(normalized, options.country);
|
|
102
|
+
} else if (hasOwnCountryCode) {
|
|
103
|
+
parsed = parsePhoneNumberFromString(normalized);
|
|
104
|
+
} else {
|
|
105
|
+
const normalizedDefault = normalizePhone(defaultCountryCode);
|
|
106
|
+
if (normalizedDefault.startsWith('+')) {
|
|
107
|
+
const combined = normalizedDefault + normalized;
|
|
108
|
+
parsed = parsePhoneNumberFromString(combined);
|
|
109
|
+
fallbackRest = combined;
|
|
110
|
+
} else {
|
|
111
|
+
parsed = undefined;
|
|
147
112
|
}
|
|
148
|
-
return normalized.replace(/(.{4})(?=.)/g, '$1 ').trim();
|
|
149
113
|
}
|
|
150
114
|
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
115
|
+
// International by default whenever the country came from the number
|
|
116
|
+
// itself (or from defaultCountryCode); national by default only when a
|
|
117
|
+
// bare number relied on an explicit options.country to resolve at all.
|
|
118
|
+
const includeCcDefault = hasOwnCountryCode || !options.country;
|
|
119
|
+
if (parsed?.isValid()) {
|
|
120
|
+
const includeCc = options.includeCountryCode ?? includeCcDefault;
|
|
121
|
+
return includeCc ? parsed.formatInternational() : parsed.formatNational();
|
|
154
122
|
}
|
|
155
|
-
|
|
156
|
-
return normalized.replace(/(.{4})(?=.)/g, '$1 ').trim();
|
|
123
|
+
return fallbackRest.startsWith('+') ? bestEffortGroup(fallbackRest.slice(1)) : fallbackRest.replace(/(.{4})(?=.)/g, '$1 ').trim();
|
|
157
124
|
}
|
|
158
125
|
//# sourceMappingURL=phone.js.map
|
package/lib/module/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export { getConfig, getConfigValue, resetConfig, setConfig } from "./config/inde
|
|
|
29
29
|
// Deep links
|
|
30
30
|
export { DeepLink, parseDeepLink, useDeepLink } from "./deepLink/index.js";
|
|
31
31
|
// Formatters
|
|
32
|
-
export { detectPhoneCountry, formatCompactNumber, formatCurrency, formatDate, formatDateTime, formatNumber, formatPercent, formatPhone, formatRelativeTime, formatTime, getInitials, isValidPhoneNumber, normalizePhone, parsePhoneNumber, parsePhoneNumberFromString } from "./formatters/index.js";
|
|
32
|
+
export { addDays, detectPhoneCountry, diffMs, formatCompactNumber, formatCurrency, formatDate, formatDateDDMMMYYYY, formatDateISO, formatDatePattern, formatDateTime, formatNumber, formatPercent, formatPhone, formatRelativeTime, formatTime, formatTime12h, formatTimeInZone, getInitials, isDateAfter, isDateBefore, isSameDay, isValidPhoneNumber, normalizePhone, parsePhoneNumber, parsePhoneNumberFromString, startOfDay } from "./formatters/index.js";
|
|
33
33
|
// Initialization
|
|
34
34
|
export { initWebority } from "./initSDK.js";
|
|
35
35
|
export { initUserAuth } from "./initUserAuth.js";
|