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