@indodev/toolkit 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENCE +21 -0
- package/README.md +293 -0
- package/dist/currency/index.cjs +245 -0
- package/dist/currency/index.cjs.map +1 -0
- package/dist/currency/index.d.cts +43 -0
- package/dist/currency/index.d.ts +43 -0
- package/dist/currency/index.js +239 -0
- package/dist/currency/index.js.map +1 -0
- package/dist/index.cjs +1173 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1153 -0
- package/dist/index.js.map +1 -0
- package/dist/nik/index.cjs +218 -0
- package/dist/nik/index.cjs.map +1 -0
- package/dist/nik/index.d.cts +288 -0
- package/dist/nik/index.d.ts +288 -0
- package/dist/nik/index.js +213 -0
- package/dist/nik/index.js.map +1 -0
- package/dist/phone/index.cjs +730 -0
- package/dist/phone/index.cjs.map +1 -0
- package/dist/phone/index.d.cts +472 -0
- package/dist/phone/index.d.ts +472 -0
- package/dist/phone/index.js +718 -0
- package/dist/phone/index.js.map +1 -0
- package/dist/words-Dy8iYkbv.d.cts +333 -0
- package/dist/words-Dy8iYkbv.d.ts +333 -0
- package/package.json +120 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1153 @@
|
|
|
1
|
+
// src/nik/constants.ts
|
|
2
|
+
var PROVINCES = {
|
|
3
|
+
"11": "Aceh",
|
|
4
|
+
"12": "Sumatera Utara",
|
|
5
|
+
"13": "Sumatera Barat",
|
|
6
|
+
"14": "Riau",
|
|
7
|
+
"15": "Jambi",
|
|
8
|
+
"16": "Sumatera Selatan",
|
|
9
|
+
"17": "Bengkulu",
|
|
10
|
+
"18": "Lampung",
|
|
11
|
+
"19": "Kepulauan Bangka Belitung",
|
|
12
|
+
"21": "Kepulauan Riau",
|
|
13
|
+
"31": "DKI Jakarta",
|
|
14
|
+
"32": "Jawa Barat",
|
|
15
|
+
"33": "Jawa Tengah",
|
|
16
|
+
"34": "DI Yogyakarta",
|
|
17
|
+
"35": "Jawa Timur",
|
|
18
|
+
"36": "Banten",
|
|
19
|
+
"51": "Bali",
|
|
20
|
+
"52": "Nusa Tenggara Barat",
|
|
21
|
+
"53": "Nusa Tenggara Timur",
|
|
22
|
+
"61": "Kalimantan Barat",
|
|
23
|
+
"62": "Kalimantan Tengah",
|
|
24
|
+
"63": "Kalimantan Selatan",
|
|
25
|
+
"64": "Kalimantan Timur",
|
|
26
|
+
"65": "Kalimantan Utara",
|
|
27
|
+
"71": "Sulawesi Utara",
|
|
28
|
+
"72": "Sulawesi Tengah",
|
|
29
|
+
"73": "Sulawesi Selatan",
|
|
30
|
+
"74": "Sulawesi Tenggara",
|
|
31
|
+
"75": "Gorontalo",
|
|
32
|
+
"76": "Sulawesi Barat",
|
|
33
|
+
"81": "Maluku",
|
|
34
|
+
"82": "Maluku Utara",
|
|
35
|
+
"91": "Papua",
|
|
36
|
+
"92": "Papua Barat",
|
|
37
|
+
"93": "Papua Selatan",
|
|
38
|
+
"94": "Papua Tengah",
|
|
39
|
+
"95": "Papua Pegunungan",
|
|
40
|
+
"96": "Papua Barat Daya"
|
|
41
|
+
};
|
|
42
|
+
var REGENCIES = {
|
|
43
|
+
"32": {
|
|
44
|
+
"01": "Kab. Bogor",
|
|
45
|
+
"02": "Kab. Sukabumi",
|
|
46
|
+
"03": "Kab. Cianjur",
|
|
47
|
+
"71": "Kota Bandung",
|
|
48
|
+
"72": "Kota Bekasi",
|
|
49
|
+
"73": "Kota Depok"
|
|
50
|
+
},
|
|
51
|
+
"31": {
|
|
52
|
+
"01": "Kota Jakarta Selatan",
|
|
53
|
+
"02": "Kota Jakarta Timur",
|
|
54
|
+
"03": "Kota Jakarta Pusat",
|
|
55
|
+
"04": "Kota Jakarta Barat",
|
|
56
|
+
"05": "Kota Jakarta Utara"
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/nik/validate.ts
|
|
61
|
+
function validateNIK(nik) {
|
|
62
|
+
if (!/^\d{16}$/.test(nik)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const provinceCode = nik.substring(0, 2);
|
|
66
|
+
if (!PROVINCES[provinceCode]) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const yearStr = nik.substring(6, 8);
|
|
70
|
+
const monthStr = nik.substring(8, 10);
|
|
71
|
+
const dayStr = nik.substring(10, 12);
|
|
72
|
+
const year = parseInt(yearStr, 10);
|
|
73
|
+
const fullYear = year > 30 ? 1900 + year : 2e3 + year;
|
|
74
|
+
const month = parseInt(monthStr, 10);
|
|
75
|
+
let day = parseInt(dayStr, 10);
|
|
76
|
+
if (day > 40) {
|
|
77
|
+
day = day - 40;
|
|
78
|
+
}
|
|
79
|
+
if (month < 1 || month > 12) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (day < 1 || day > 31) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const testDate = new Date(fullYear, month - 1, day);
|
|
86
|
+
if (testDate.getFullYear() !== fullYear || testDate.getMonth() !== month - 1 || testDate.getDate() !== day) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const now = /* @__PURE__ */ new Date();
|
|
90
|
+
if (testDate > now || testDate < new Date(1900, 0, 1)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/nik/parse.ts
|
|
97
|
+
function parseNIK(nik) {
|
|
98
|
+
if (!/^\d{16}$/.test(nik)) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const provinceCode = nik.substring(0, 2);
|
|
102
|
+
const regencyCode = nik.substring(2, 4);
|
|
103
|
+
const districtCode = nik.substring(4, 6);
|
|
104
|
+
const yearStr = nik.substring(6, 8);
|
|
105
|
+
const monthStr = nik.substring(8, 10);
|
|
106
|
+
const dayStr = nik.substring(10, 12);
|
|
107
|
+
const serialNumber = nik.substring(12, 16);
|
|
108
|
+
const province = PROVINCES[provinceCode];
|
|
109
|
+
if (!province) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const regencies = REGENCIES[provinceCode] || {};
|
|
113
|
+
const regency = regencies[regencyCode] || "Unknown";
|
|
114
|
+
let day = parseInt(dayStr, 10);
|
|
115
|
+
const month = parseInt(monthStr, 10);
|
|
116
|
+
const year = parseInt(yearStr, 10);
|
|
117
|
+
let gender = null;
|
|
118
|
+
if (day > 40) {
|
|
119
|
+
gender = "female";
|
|
120
|
+
day -= 40;
|
|
121
|
+
} else {
|
|
122
|
+
gender = "male";
|
|
123
|
+
}
|
|
124
|
+
const fullYear = year > 30 ? 1900 + year : 2e3 + year;
|
|
125
|
+
const birthDate = new Date(fullYear, month - 1, day);
|
|
126
|
+
if (birthDate.getFullYear() !== fullYear || birthDate.getMonth() !== month - 1 || birthDate.getDate() !== day) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
province: {
|
|
131
|
+
code: provinceCode,
|
|
132
|
+
name: province
|
|
133
|
+
},
|
|
134
|
+
regency: {
|
|
135
|
+
code: regencyCode,
|
|
136
|
+
name: regency
|
|
137
|
+
},
|
|
138
|
+
district: {
|
|
139
|
+
code: districtCode,
|
|
140
|
+
name: null
|
|
141
|
+
},
|
|
142
|
+
birthDate,
|
|
143
|
+
gender,
|
|
144
|
+
serialNumber,
|
|
145
|
+
isValid: true
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/nik/format.ts
|
|
150
|
+
function formatNIK(nik, separator = "-") {
|
|
151
|
+
if (!/^\d{16}$/.test(nik)) {
|
|
152
|
+
return nik;
|
|
153
|
+
}
|
|
154
|
+
return [
|
|
155
|
+
nik.substring(0, 2),
|
|
156
|
+
// Province
|
|
157
|
+
nik.substring(2, 4),
|
|
158
|
+
// Regency
|
|
159
|
+
nik.substring(4, 6),
|
|
160
|
+
// District
|
|
161
|
+
nik.substring(6, 8),
|
|
162
|
+
// Year
|
|
163
|
+
nik.substring(8, 10),
|
|
164
|
+
// Month
|
|
165
|
+
nik.substring(10, 12),
|
|
166
|
+
// Day
|
|
167
|
+
nik.substring(12, 16)
|
|
168
|
+
// Serial
|
|
169
|
+
].join(separator);
|
|
170
|
+
}
|
|
171
|
+
function maskNIK(nik, options = {}) {
|
|
172
|
+
if (!/^\d{16}$/.test(nik)) {
|
|
173
|
+
return nik;
|
|
174
|
+
}
|
|
175
|
+
const { start = 4, end = 4, char = "*", separator } = options;
|
|
176
|
+
if (start + end >= 16) {
|
|
177
|
+
return nik;
|
|
178
|
+
}
|
|
179
|
+
if (separator) {
|
|
180
|
+
const formatted = formatNIK(nik, separator);
|
|
181
|
+
const parts = formatted.split(separator);
|
|
182
|
+
let charCount = 0;
|
|
183
|
+
const maskedParts = parts.map((part) => {
|
|
184
|
+
const partStart = charCount;
|
|
185
|
+
const partEnd = charCount + part.length;
|
|
186
|
+
charCount += part.length;
|
|
187
|
+
if (partEnd <= start) {
|
|
188
|
+
return part;
|
|
189
|
+
} else if (partStart >= 16 - end) {
|
|
190
|
+
return part;
|
|
191
|
+
} else if (partStart >= start && partEnd <= 16 - end) {
|
|
192
|
+
return char.repeat(part.length);
|
|
193
|
+
} else {
|
|
194
|
+
return part.split("").map((ch, idx) => {
|
|
195
|
+
const pos = partStart + idx;
|
|
196
|
+
if (pos < start || pos >= 16 - end) {
|
|
197
|
+
return ch;
|
|
198
|
+
}
|
|
199
|
+
return char;
|
|
200
|
+
}).join("");
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
return maskedParts.join(separator);
|
|
204
|
+
}
|
|
205
|
+
const startPart = nik.substring(0, start);
|
|
206
|
+
const endPart = nik.substring(16 - end);
|
|
207
|
+
const maskLength = 16 - start - end;
|
|
208
|
+
return startPart + char.repeat(maskLength) + endPart;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/phone/constants.ts
|
|
212
|
+
var OPERATOR_PREFIXES = {
|
|
213
|
+
// Telkomsel (Halo, Simpati, by.U)
|
|
214
|
+
"0811": "Telkomsel",
|
|
215
|
+
"0812": "Telkomsel",
|
|
216
|
+
"0813": "Telkomsel",
|
|
217
|
+
"0821": "Telkomsel",
|
|
218
|
+
"0822": "Telkomsel",
|
|
219
|
+
"0823": "Telkomsel",
|
|
220
|
+
"0851": "Telkomsel",
|
|
221
|
+
"0852": "Telkomsel",
|
|
222
|
+
"0853": "Telkomsel",
|
|
223
|
+
// XL Axiata (XL Prepaid, XL Prioritas, LIVE.ON)
|
|
224
|
+
"0817": "XL",
|
|
225
|
+
"0818": "XL",
|
|
226
|
+
"0819": "XL",
|
|
227
|
+
"0859": "XL",
|
|
228
|
+
"0877": "XL",
|
|
229
|
+
"0878": "XL",
|
|
230
|
+
"0879": "XL",
|
|
231
|
+
// Indosat Ooredoo (IM3, Mentari)
|
|
232
|
+
// Note: Tri (3 Indonesia) merged with Indosat
|
|
233
|
+
"0814": "Indosat",
|
|
234
|
+
"0815": "Indosat",
|
|
235
|
+
"0816": "Indosat",
|
|
236
|
+
"0855": "Indosat",
|
|
237
|
+
"0856": "Indosat",
|
|
238
|
+
"0857": "Indosat",
|
|
239
|
+
"0858": "Indosat",
|
|
240
|
+
"0895": "Indosat",
|
|
241
|
+
"0896": "Indosat",
|
|
242
|
+
"0897": "Indosat",
|
|
243
|
+
"0898": "Indosat",
|
|
244
|
+
"0899": "Indosat",
|
|
245
|
+
// Smartfren (Smartfren Power Up)
|
|
246
|
+
"0881": "Smartfren",
|
|
247
|
+
"0882": "Smartfren",
|
|
248
|
+
"0883": "Smartfren",
|
|
249
|
+
"0884": "Smartfren",
|
|
250
|
+
"0885": "Smartfren",
|
|
251
|
+
"0886": "Smartfren",
|
|
252
|
+
"0887": "Smartfren",
|
|
253
|
+
"0888": "Smartfren",
|
|
254
|
+
"0889": "Smartfren",
|
|
255
|
+
// Axis (Acquired by XL but maintains separate branding)
|
|
256
|
+
"0831": "Axis",
|
|
257
|
+
"0832": "Axis",
|
|
258
|
+
"0833": "Axis",
|
|
259
|
+
"0838": "Axis"
|
|
260
|
+
};
|
|
261
|
+
var AREA_CODES = {
|
|
262
|
+
// ========================================
|
|
263
|
+
// JAKARTA, BANTEN & WEST JAVA
|
|
264
|
+
// ========================================
|
|
265
|
+
// Jakarta & Greater Jakarta
|
|
266
|
+
"021": "Jakarta",
|
|
267
|
+
// Banten
|
|
268
|
+
"0252": "Lebak",
|
|
269
|
+
"0253": "Pandeglang",
|
|
270
|
+
"0254": "Cilegon & Serang",
|
|
271
|
+
// West Java
|
|
272
|
+
"022": "Bandung",
|
|
273
|
+
"0231": "Cirebon",
|
|
274
|
+
"0232": "Kuningan",
|
|
275
|
+
"0233": "Majalengka",
|
|
276
|
+
"0234": "Indramayu",
|
|
277
|
+
"0251": "Bogor",
|
|
278
|
+
"0260": "Subang",
|
|
279
|
+
"0261": "Sumedang",
|
|
280
|
+
"0262": "Garut",
|
|
281
|
+
"0263": "Cianjur",
|
|
282
|
+
"0264": "Purwakarta",
|
|
283
|
+
"0265": "Tasikmalaya",
|
|
284
|
+
"0266": "Sukabumi",
|
|
285
|
+
"0267": "Karawang",
|
|
286
|
+
// ========================================
|
|
287
|
+
// CENTRAL JAVA & YOGYAKARTA
|
|
288
|
+
// ========================================
|
|
289
|
+
// Central Java
|
|
290
|
+
"024": "Semarang",
|
|
291
|
+
"0271": "Solo",
|
|
292
|
+
"0272": "Klaten",
|
|
293
|
+
"0273": "Wonogiri",
|
|
294
|
+
"0275": "Purworejo",
|
|
295
|
+
"0276": "Boyolali",
|
|
296
|
+
"0280": "Cilacap",
|
|
297
|
+
"0281": "Banyumas & Purbalingga",
|
|
298
|
+
"0282": "Cilacap",
|
|
299
|
+
"0283": "Tegal & Brebes",
|
|
300
|
+
"0284": "Pemalang",
|
|
301
|
+
"0285": "Pekalongan",
|
|
302
|
+
"0286": "Banjarnegara & Wonosobo",
|
|
303
|
+
"0287": "Kebumen",
|
|
304
|
+
"0289": "Bumiayu",
|
|
305
|
+
"0291": "Kudus & Jepara",
|
|
306
|
+
"0292": "Grobogan",
|
|
307
|
+
"0293": "Magelang",
|
|
308
|
+
"0294": "Kendal",
|
|
309
|
+
"0295": "Pati & Rembang",
|
|
310
|
+
"0296": "Blora",
|
|
311
|
+
"0297": "Karimun Jawa",
|
|
312
|
+
"0298": "Salatiga",
|
|
313
|
+
// Yogyakarta
|
|
314
|
+
"0274": "Yogyakarta",
|
|
315
|
+
// ========================================
|
|
316
|
+
// EAST JAVA, BALI & NUSA TENGGARA
|
|
317
|
+
// ========================================
|
|
318
|
+
// East Java
|
|
319
|
+
"031": "Surabaya",
|
|
320
|
+
"0321": "Mojokerto & Jombang",
|
|
321
|
+
"0322": "Lamongan",
|
|
322
|
+
"0323": "Sampang",
|
|
323
|
+
"0324": "Pamekasan",
|
|
324
|
+
"0325": "Bawean",
|
|
325
|
+
"0326": "Masalembu",
|
|
326
|
+
"0327": "Kangean",
|
|
327
|
+
"0328": "Sumenep",
|
|
328
|
+
"0331": "Jember",
|
|
329
|
+
"0332": "Bondowoso",
|
|
330
|
+
"0333": "Banyuwangi",
|
|
331
|
+
"0334": "Lumajang",
|
|
332
|
+
"0335": "Probolinggo",
|
|
333
|
+
"0336": "Jember",
|
|
334
|
+
"0338": "Situbondo",
|
|
335
|
+
"0341": "Malang",
|
|
336
|
+
"0342": "Blitar",
|
|
337
|
+
"0343": "Pasuruan",
|
|
338
|
+
"0351": "Madiun",
|
|
339
|
+
"0352": "Ponorogo",
|
|
340
|
+
"0353": "Bojonegoro",
|
|
341
|
+
"0354": "Kediri",
|
|
342
|
+
"0355": "Tulungagung",
|
|
343
|
+
"0356": "Tuban",
|
|
344
|
+
"0357": "Pacitan",
|
|
345
|
+
"0358": "Nganjuk",
|
|
346
|
+
// Bali
|
|
347
|
+
"0361": "Denpasar",
|
|
348
|
+
"0362": "Singaraja",
|
|
349
|
+
"0363": "Amlapura",
|
|
350
|
+
"0365": "Negara",
|
|
351
|
+
"0366": "Tabanan",
|
|
352
|
+
"0368": "Gianyar",
|
|
353
|
+
// Nusa Tenggara Barat (NTB)
|
|
354
|
+
"0370": "Mataram",
|
|
355
|
+
"0371": "Sumbawa",
|
|
356
|
+
"0372": "West Sumbawa",
|
|
357
|
+
"0373": "Dompu",
|
|
358
|
+
"0374": "Bima",
|
|
359
|
+
"0376": "East Lombok",
|
|
360
|
+
// Nusa Tenggara Timur (NTT)
|
|
361
|
+
"0379": "Alor",
|
|
362
|
+
"0380": "Kupang",
|
|
363
|
+
"0381": "Ende",
|
|
364
|
+
"0382": "Sikka",
|
|
365
|
+
"0383": "East Flores",
|
|
366
|
+
"0384": "Ngada",
|
|
367
|
+
"0385": "Manggarai",
|
|
368
|
+
"0386": "West Manggarai",
|
|
369
|
+
"0387": "Sumba",
|
|
370
|
+
"0388": "North & South Central Timor",
|
|
371
|
+
"0389": "Belu",
|
|
372
|
+
// ========================================
|
|
373
|
+
// SULAWESI
|
|
374
|
+
// ========================================
|
|
375
|
+
// South Sulawesi
|
|
376
|
+
"0410": "Pangkajene",
|
|
377
|
+
"0411": "Makassar",
|
|
378
|
+
"0413": "Bantaeng & Bulukumba",
|
|
379
|
+
"0414": "Selayar",
|
|
380
|
+
"0417": "Malino",
|
|
381
|
+
"0418": "Takalar",
|
|
382
|
+
"0419": "Jeneponto",
|
|
383
|
+
"0420": "Enrekang",
|
|
384
|
+
"0421": "Pare Pare",
|
|
385
|
+
"0423": "Tana Toraja",
|
|
386
|
+
"0427": "Barru",
|
|
387
|
+
"0471": "Luwu",
|
|
388
|
+
"0472": "Wajo (Pitumpanua)",
|
|
389
|
+
"0473": "North Luwu",
|
|
390
|
+
"0474": "East Luwu",
|
|
391
|
+
"0475": "Sorowako",
|
|
392
|
+
"0481": "Bone",
|
|
393
|
+
"0482": "Sinjai",
|
|
394
|
+
"0484": "Soppeng",
|
|
395
|
+
"0485": "Wajo",
|
|
396
|
+
// West Sulawesi
|
|
397
|
+
"0422": "Majene",
|
|
398
|
+
"0426": "Mamuju",
|
|
399
|
+
"0428": "Polewali",
|
|
400
|
+
"0429": "Central Mamuju",
|
|
401
|
+
// Central Sulawesi
|
|
402
|
+
"0409": "Morowali",
|
|
403
|
+
"0445": "Buol",
|
|
404
|
+
"0450": "Parigi Moutong",
|
|
405
|
+
"0451": "Palu",
|
|
406
|
+
"0452": "Poso",
|
|
407
|
+
"0453": "Toli-Toli",
|
|
408
|
+
"0454": "Tinombo",
|
|
409
|
+
"0455": "Moutong",
|
|
410
|
+
"0457": "Donggala",
|
|
411
|
+
"0458": "Tentena",
|
|
412
|
+
"0461": "Banggai",
|
|
413
|
+
"0462": "Banggai Island",
|
|
414
|
+
"0463": "Bunta",
|
|
415
|
+
"0464": "Tojo Una-Una",
|
|
416
|
+
"0465": "North Morowali",
|
|
417
|
+
// Southeast Sulawesi
|
|
418
|
+
"0401": "Kendari",
|
|
419
|
+
"0402": "Buton",
|
|
420
|
+
"0403": "Muna",
|
|
421
|
+
"0404": "Wakatobi",
|
|
422
|
+
"0405": "Kolaka",
|
|
423
|
+
"0408": "Konawe",
|
|
424
|
+
// North Sulawesi
|
|
425
|
+
"0430": "South Minahasa",
|
|
426
|
+
"0431": "Manado",
|
|
427
|
+
"0432": "Sangihe",
|
|
428
|
+
"0433": "Talaud",
|
|
429
|
+
"0434": "Bolaang Mongondow",
|
|
430
|
+
"0438": "Bitung",
|
|
431
|
+
// Gorontalo
|
|
432
|
+
"0435": "Gorontalo",
|
|
433
|
+
"0442": "North Gorontalo",
|
|
434
|
+
"0443": "Pohuwato",
|
|
435
|
+
// ========================================
|
|
436
|
+
// KALIMANTAN
|
|
437
|
+
// ========================================
|
|
438
|
+
// West Kalimantan
|
|
439
|
+
"0534": "Ketapang",
|
|
440
|
+
"0535": "Kayong Utara",
|
|
441
|
+
"0561": "Pontianak",
|
|
442
|
+
"0562": "Sambas & Singkawang",
|
|
443
|
+
"0563": "Landak",
|
|
444
|
+
"0564": "Sanggau",
|
|
445
|
+
"0565": "Sintang",
|
|
446
|
+
"0567": "Kapuas Hulu",
|
|
447
|
+
"0568": "Melawi",
|
|
448
|
+
// Central Kalimantan
|
|
449
|
+
"0513": "Kapuas",
|
|
450
|
+
"0519": "North Barito",
|
|
451
|
+
"0526": "South & East Barito",
|
|
452
|
+
"0528": "Murung Raya",
|
|
453
|
+
"0531": "East Kotawaringin",
|
|
454
|
+
"0532": "West Kotawaringin",
|
|
455
|
+
"0536": "Palangka Raya",
|
|
456
|
+
"0537": "Gunung Mas",
|
|
457
|
+
"0538": "Seruyan",
|
|
458
|
+
"0539": "Seruyan & East Kotawaringin",
|
|
459
|
+
// South Kalimantan
|
|
460
|
+
"0511": "Banjarmasin",
|
|
461
|
+
"0512": "Tanah Laut",
|
|
462
|
+
"0517": "Hulu Sungai Selatan",
|
|
463
|
+
"0518": "Tanah Bumbu",
|
|
464
|
+
"0527": "Hulu Sungai Utara",
|
|
465
|
+
// East Kalimantan
|
|
466
|
+
"0541": "Samarinda",
|
|
467
|
+
"0542": "Balikpapan",
|
|
468
|
+
"0543": "Paser",
|
|
469
|
+
"0545": "West Kutai",
|
|
470
|
+
"0548": "Bontang",
|
|
471
|
+
"0549": "East Kutai",
|
|
472
|
+
"0554": "Berau",
|
|
473
|
+
// North Kalimantan
|
|
474
|
+
"0551": "Tarakan",
|
|
475
|
+
"0552": "Bulungan",
|
|
476
|
+
"0553": "Malinau",
|
|
477
|
+
"0556": "Nunukan",
|
|
478
|
+
// ========================================
|
|
479
|
+
// SUMATRA
|
|
480
|
+
// ========================================
|
|
481
|
+
// Aceh
|
|
482
|
+
"0627": "Subulussalam & Dairi (North Sumatra)",
|
|
483
|
+
"0629": "Southeast Aceh",
|
|
484
|
+
"0641": "Langsa",
|
|
485
|
+
"0642": "Gayo Lues",
|
|
486
|
+
"0643": "Central Aceh",
|
|
487
|
+
"0644": "Bireuen",
|
|
488
|
+
"0645": "Lhokseumawe",
|
|
489
|
+
"0646": "East Aceh",
|
|
490
|
+
"0650": "Simeulue",
|
|
491
|
+
"0651": "Banda Aceh",
|
|
492
|
+
"0652": "Sabang",
|
|
493
|
+
"0653": "Pidie",
|
|
494
|
+
"0654": "Aceh Jaya",
|
|
495
|
+
"0655": "West Aceh",
|
|
496
|
+
"0656": "South Aceh",
|
|
497
|
+
"0657": "South Aceh",
|
|
498
|
+
"0658": "Singkil",
|
|
499
|
+
"0659": "Southwest Aceh",
|
|
500
|
+
// North Sumatra
|
|
501
|
+
"061": "Medan",
|
|
502
|
+
"0620": "Pangkalan Brandan",
|
|
503
|
+
"0621": "Tebing Tinggi",
|
|
504
|
+
"0622": "Pematang Siantar",
|
|
505
|
+
"0623": "Asahan",
|
|
506
|
+
"0624": "Labuhan Batu",
|
|
507
|
+
"0625": "Parapat",
|
|
508
|
+
"0626": "Samosir",
|
|
509
|
+
// '0627': 'Dairi', // for this prefix, it same with Subulussalam (Aceh)
|
|
510
|
+
"0628": "Karo",
|
|
511
|
+
"0630": "South Nias",
|
|
512
|
+
"0631": "Sibolga",
|
|
513
|
+
"0632": "Toba Samosir",
|
|
514
|
+
"0633": "North Tapanuli",
|
|
515
|
+
"0634": "Padang Sidempuan",
|
|
516
|
+
"0635": "South Tapanuli",
|
|
517
|
+
"0636": "Mandailing Natal",
|
|
518
|
+
"0638": "Barus",
|
|
519
|
+
"0639": "Nias",
|
|
520
|
+
// West Sumatra
|
|
521
|
+
"0751": "Padang",
|
|
522
|
+
"0752": "Bukittinggi",
|
|
523
|
+
"0753": "Pasaman",
|
|
524
|
+
"0754": "Sawahlunto",
|
|
525
|
+
"0755": "Solok",
|
|
526
|
+
"0756": "South Pesisir",
|
|
527
|
+
"0757": "South Pesisir",
|
|
528
|
+
"0759": "Mentawai",
|
|
529
|
+
// Riau
|
|
530
|
+
"0760": "Kuantan Singingi",
|
|
531
|
+
"0761": "Pekanbaru",
|
|
532
|
+
"0762": "Kampar",
|
|
533
|
+
"0763": "Bengkalis",
|
|
534
|
+
"0764": "Siak",
|
|
535
|
+
"0765": "Dumai",
|
|
536
|
+
"0766": "Bengkalis",
|
|
537
|
+
"0767": "Rokan Hulu",
|
|
538
|
+
"0768": "Indragiri Hilir",
|
|
539
|
+
"0769": "Indragiri Hulu",
|
|
540
|
+
// Riau Islands
|
|
541
|
+
"0770": "Muka Kuning Batamindo",
|
|
542
|
+
"0771": "Tanjungpinang",
|
|
543
|
+
"0772": "Anambas",
|
|
544
|
+
"0773": "Natuna",
|
|
545
|
+
"0776": "Lingga",
|
|
546
|
+
"0777": "Great Karimun",
|
|
547
|
+
"0778": "Batam",
|
|
548
|
+
"0779": "Kundur",
|
|
549
|
+
// Jambi
|
|
550
|
+
"0741": "Jambi",
|
|
551
|
+
"0742": "West Tanjung Jabung",
|
|
552
|
+
"0743": "Batanghari",
|
|
553
|
+
"0744": "Tebo",
|
|
554
|
+
"0745": "Sarolangun",
|
|
555
|
+
"0746": "Merangin",
|
|
556
|
+
"0747": "Bungo",
|
|
557
|
+
"0748": "Kerinci",
|
|
558
|
+
// South Sumatra
|
|
559
|
+
"0702": "Empat Lawang",
|
|
560
|
+
"0711": "Palembang",
|
|
561
|
+
"0712": "Ogan Komering Ilir",
|
|
562
|
+
"0713": "Prabumulih",
|
|
563
|
+
"0714": "Musi Banyuasin",
|
|
564
|
+
"0730": "Pagar Alam",
|
|
565
|
+
"0731": "Lahat",
|
|
566
|
+
"0733": "Lubuklinggau",
|
|
567
|
+
"0734": "Muara Enim",
|
|
568
|
+
"0735": "Ogan Komering Ulu",
|
|
569
|
+
// Bangka Belitung
|
|
570
|
+
"0715": "Belinyu",
|
|
571
|
+
"0716": "West Bangka",
|
|
572
|
+
"0717": "Pangkal Pinang",
|
|
573
|
+
"0718": "Central & South Bangka",
|
|
574
|
+
"0719": "Belitung",
|
|
575
|
+
// Bengkulu
|
|
576
|
+
"0732": "Rejang Lebong",
|
|
577
|
+
"0736": "Bengkulu",
|
|
578
|
+
"0737": "North Bengkulu",
|
|
579
|
+
"0739": "South Bengkulu",
|
|
580
|
+
// Lampung
|
|
581
|
+
"0721": "Bandar Lampung",
|
|
582
|
+
"0722": "Tanggamus",
|
|
583
|
+
"0723": "Way Kanan",
|
|
584
|
+
"0724": "North Lampung",
|
|
585
|
+
"0725": "Metro",
|
|
586
|
+
"0726": "Tulang Bawang",
|
|
587
|
+
"0727": "South Lampung",
|
|
588
|
+
"0728": "West Lampung",
|
|
589
|
+
"0729": "Pringsewu",
|
|
590
|
+
// ========================================
|
|
591
|
+
// MALUKU & PAPUA
|
|
592
|
+
// ========================================
|
|
593
|
+
// Maluku
|
|
594
|
+
"0910": "Ambon",
|
|
595
|
+
"0911": "Southeast Maluku",
|
|
596
|
+
"0913": "Tual",
|
|
597
|
+
"0914": "Saumlaki",
|
|
598
|
+
"0916": "Namlea",
|
|
599
|
+
"0918": "Ternate",
|
|
600
|
+
"0921": "Sanana",
|
|
601
|
+
"0924": "Tobelo",
|
|
602
|
+
// Papua & West Papua
|
|
603
|
+
"0901": "Timika",
|
|
604
|
+
"0902": "Agats",
|
|
605
|
+
"0951": "Sorong",
|
|
606
|
+
"0952": "South Sorong",
|
|
607
|
+
"0967": "Manokwari",
|
|
608
|
+
"0969": "Sorong",
|
|
609
|
+
"0971": "Merauke",
|
|
610
|
+
"0975": "Boven Digoel",
|
|
611
|
+
"0979": "Tembagapura",
|
|
612
|
+
"0981": "Jayapura",
|
|
613
|
+
"0986": "Wamena"
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
// src/phone/validate.ts
|
|
617
|
+
function validatePhoneNumber(phone) {
|
|
618
|
+
if (!phone || typeof phone !== "string") {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
if (!/^[\d\s\-+().]+$/.test(phone)) {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
const cleaned = phone.replace(/[\s\-().]/g, "");
|
|
625
|
+
let normalized;
|
|
626
|
+
if (cleaned.startsWith("+62")) {
|
|
627
|
+
normalized = "0" + cleaned.substring(3);
|
|
628
|
+
} else if (cleaned.startsWith("62") && !cleaned.startsWith("620")) {
|
|
629
|
+
normalized = "0" + cleaned.substring(2);
|
|
630
|
+
} else if (cleaned.startsWith("0")) {
|
|
631
|
+
normalized = cleaned;
|
|
632
|
+
} else {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
if (normalized.startsWith("08")) {
|
|
636
|
+
return validateMobileNumber(normalized);
|
|
637
|
+
}
|
|
638
|
+
if (normalized.startsWith("0")) {
|
|
639
|
+
return validateLandlineNumber(normalized);
|
|
640
|
+
}
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
function validateMobileNumber(phone) {
|
|
644
|
+
if (phone.length < 10 || phone.length > 13) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
const prefix = phone.substring(0, 4);
|
|
648
|
+
if (!OPERATOR_PREFIXES[prefix]) {
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
if (!/^\d+$/.test(phone)) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
function validateLandlineNumber(phone) {
|
|
657
|
+
if (phone.length < 9 || phone.length > 11) {
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
const areaCode3 = phone.substring(0, 3);
|
|
661
|
+
const areaCode4 = phone.substring(0, 4);
|
|
662
|
+
if (AREA_CODES[areaCode3] || AREA_CODES[areaCode4]) {
|
|
663
|
+
return true;
|
|
664
|
+
}
|
|
665
|
+
if (/^0[2-9]\d{7,9}$/.test(phone)) {
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
function isMobileNumber(phone) {
|
|
671
|
+
if (!validatePhoneNumber(phone)) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
const cleaned = phone.replace(/[^\d+]/g, "");
|
|
675
|
+
let normalized;
|
|
676
|
+
if (cleaned.startsWith("+62")) {
|
|
677
|
+
normalized = "0" + cleaned.substring(3);
|
|
678
|
+
} else if (cleaned.startsWith("62")) {
|
|
679
|
+
normalized = "0" + cleaned.substring(2);
|
|
680
|
+
} else {
|
|
681
|
+
normalized = cleaned;
|
|
682
|
+
}
|
|
683
|
+
return normalized.startsWith("08");
|
|
684
|
+
}
|
|
685
|
+
function isLandlineNumber(phone) {
|
|
686
|
+
if (!validatePhoneNumber(phone)) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
return !isMobileNumber(phone);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/phone/format.ts
|
|
693
|
+
function formatPhoneNumber(phone, format = "national") {
|
|
694
|
+
if (!validatePhoneNumber(phone)) {
|
|
695
|
+
return phone;
|
|
696
|
+
}
|
|
697
|
+
const cleaned = cleanPhoneNumber(phone);
|
|
698
|
+
let normalized;
|
|
699
|
+
if (cleaned.startsWith("+62")) {
|
|
700
|
+
normalized = "0" + cleaned.substring(3);
|
|
701
|
+
} else if (cleaned.startsWith("62") && !cleaned.startsWith("620")) {
|
|
702
|
+
normalized = "0" + cleaned.substring(2);
|
|
703
|
+
} else {
|
|
704
|
+
normalized = cleaned;
|
|
705
|
+
}
|
|
706
|
+
switch (format) {
|
|
707
|
+
case "international":
|
|
708
|
+
return toInternational(normalized);
|
|
709
|
+
case "national":
|
|
710
|
+
case "display":
|
|
711
|
+
return toNational(normalized);
|
|
712
|
+
case "e164":
|
|
713
|
+
return toE164(normalized);
|
|
714
|
+
default:
|
|
715
|
+
return phone;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function toInternational(phone) {
|
|
719
|
+
const cleaned = cleanPhoneNumber(phone);
|
|
720
|
+
if (!cleaned) {
|
|
721
|
+
return phone;
|
|
722
|
+
}
|
|
723
|
+
const normalized = normalizeToNational(cleaned);
|
|
724
|
+
if (!normalized) {
|
|
725
|
+
return phone;
|
|
726
|
+
}
|
|
727
|
+
const withoutZero = normalized.substring(1);
|
|
728
|
+
if (normalized.startsWith("08")) {
|
|
729
|
+
if (withoutZero.length === 11) {
|
|
730
|
+
return `+62 ${withoutZero.substring(0, 3)}-${withoutZero.substring(3, 7)}-${withoutZero.substring(7)}`;
|
|
731
|
+
} else if (withoutZero.length === 10) {
|
|
732
|
+
return `+62 ${withoutZero.substring(0, 3)}-${withoutZero.substring(3, 6)}-${withoutZero.substring(6)}`;
|
|
733
|
+
} else if (withoutZero.length === 9) {
|
|
734
|
+
return `+62 ${withoutZero.substring(0, 3)}-${withoutZero.substring(3)}`;
|
|
735
|
+
} else if (withoutZero.length === 12) {
|
|
736
|
+
return `+62 ${withoutZero.substring(0, 3)}-${withoutZero.substring(3, 7)}-${withoutZero.substring(7)}`;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
const areaCodeLength = getAreaCodeLength(normalized);
|
|
740
|
+
const areaCode = normalized.substring(1, areaCodeLength + 1);
|
|
741
|
+
const localNumber = normalized.substring(areaCodeLength + 1);
|
|
742
|
+
return `+62 ${areaCode}-${localNumber}`;
|
|
743
|
+
}
|
|
744
|
+
function toNational(phone) {
|
|
745
|
+
const cleaned = cleanPhoneNumber(phone);
|
|
746
|
+
if (!cleaned) {
|
|
747
|
+
return phone;
|
|
748
|
+
}
|
|
749
|
+
const normalized = normalizeToNational(cleaned);
|
|
750
|
+
if (!normalized) {
|
|
751
|
+
return phone;
|
|
752
|
+
}
|
|
753
|
+
if (normalized.startsWith("08")) {
|
|
754
|
+
if (normalized.length === 12) {
|
|
755
|
+
return `${normalized.substring(0, 4)}-${normalized.substring(4, 8)}-${normalized.substring(8)}`;
|
|
756
|
+
} else if (normalized.length === 11) {
|
|
757
|
+
return `${normalized.substring(0, 4)}-${normalized.substring(4, 7)}-${normalized.substring(7)}`;
|
|
758
|
+
} else if (normalized.length === 10) {
|
|
759
|
+
return `${normalized.substring(0, 4)}-${normalized.substring(4)}`;
|
|
760
|
+
} else if (normalized.length === 13) {
|
|
761
|
+
return `${normalized.substring(0, 4)}-${normalized.substring(4, 8)}-${normalized.substring(8)}`;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
const areaCodeLength = getAreaCodeLength(normalized);
|
|
765
|
+
const areaCodeWithZero = normalized.substring(0, areaCodeLength + 1);
|
|
766
|
+
const localNumber = normalized.substring(areaCodeLength + 1);
|
|
767
|
+
return `${areaCodeWithZero}-${localNumber}`;
|
|
768
|
+
}
|
|
769
|
+
function toE164(phone) {
|
|
770
|
+
const cleaned = cleanPhoneNumber(phone);
|
|
771
|
+
if (!cleaned) {
|
|
772
|
+
return phone;
|
|
773
|
+
}
|
|
774
|
+
const normalized = normalizeToNational(cleaned);
|
|
775
|
+
if (!normalized) {
|
|
776
|
+
return phone;
|
|
777
|
+
}
|
|
778
|
+
return "62" + normalized.substring(1);
|
|
779
|
+
}
|
|
780
|
+
function cleanPhoneNumber(phone) {
|
|
781
|
+
if (!phone || typeof phone !== "string") {
|
|
782
|
+
return "";
|
|
783
|
+
}
|
|
784
|
+
return phone.replace(/[^\d+]/g, "");
|
|
785
|
+
}
|
|
786
|
+
function normalizeToNational(phone) {
|
|
787
|
+
if (phone.startsWith("+62")) {
|
|
788
|
+
return "0" + phone.substring(3);
|
|
789
|
+
} else if (phone.startsWith("62") && !phone.startsWith("620")) {
|
|
790
|
+
return "0" + phone.substring(2);
|
|
791
|
+
} else if (phone.startsWith("0")) {
|
|
792
|
+
return phone;
|
|
793
|
+
}
|
|
794
|
+
return "";
|
|
795
|
+
}
|
|
796
|
+
function getAreaCodeLength(normalized) {
|
|
797
|
+
const fourDigitCode = normalized.substring(0, 5);
|
|
798
|
+
if (AREA_CODES[fourDigitCode]) {
|
|
799
|
+
return 4;
|
|
800
|
+
}
|
|
801
|
+
const threeDigitCode = normalized.substring(0, 4);
|
|
802
|
+
if (AREA_CODES[threeDigitCode]) {
|
|
803
|
+
return 3;
|
|
804
|
+
}
|
|
805
|
+
const twoDigitCode = normalized.substring(0, 3);
|
|
806
|
+
if (AREA_CODES[twoDigitCode]) {
|
|
807
|
+
return 2;
|
|
808
|
+
}
|
|
809
|
+
return 2;
|
|
810
|
+
}
|
|
811
|
+
function maskPhoneNumber(phone, options = {}) {
|
|
812
|
+
const cleaned = cleanPhoneNumber(phone);
|
|
813
|
+
if (!cleaned) {
|
|
814
|
+
return phone;
|
|
815
|
+
}
|
|
816
|
+
const isInternational = cleaned.startsWith("+");
|
|
817
|
+
let toMask;
|
|
818
|
+
if (isInternational) {
|
|
819
|
+
toMask = cleaned;
|
|
820
|
+
} else {
|
|
821
|
+
const normalized = normalizeToNational(cleaned);
|
|
822
|
+
toMask = normalized || cleaned;
|
|
823
|
+
}
|
|
824
|
+
if (toMask.length < 4) {
|
|
825
|
+
return phone;
|
|
826
|
+
}
|
|
827
|
+
const { char = "*", separator } = options;
|
|
828
|
+
let { start = 4, end = 4 } = options;
|
|
829
|
+
if (start + end >= toMask.length) {
|
|
830
|
+
if (toMask.length < 10) {
|
|
831
|
+
const minMaskLength = 1;
|
|
832
|
+
const availableForVisible = toMask.length - minMaskLength;
|
|
833
|
+
if (availableForVisible >= 2) {
|
|
834
|
+
start = Math.floor(availableForVisible / 2);
|
|
835
|
+
end = availableForVisible - start;
|
|
836
|
+
} else {
|
|
837
|
+
return toMask;
|
|
838
|
+
}
|
|
839
|
+
} else {
|
|
840
|
+
return toMask;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
const startPart = toMask.substring(0, start);
|
|
844
|
+
const endPart = toMask.substring(toMask.length - end);
|
|
845
|
+
const maskLength = toMask.length - start - end;
|
|
846
|
+
const masked = startPart + char.repeat(maskLength) + endPart;
|
|
847
|
+
if (separator) {
|
|
848
|
+
return `${masked.substring(0, start)}${separator}${masked.substring(start, masked.length - end)}${separator}${masked.substring(masked.length - end)}`;
|
|
849
|
+
}
|
|
850
|
+
return masked;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// src/phone/parse.ts
|
|
854
|
+
function parsePhoneNumber(phone) {
|
|
855
|
+
if (!validatePhoneNumber(phone)) {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
const cleaned = cleanPhoneNumber(phone);
|
|
859
|
+
const normalized = normalizeToNational2(cleaned);
|
|
860
|
+
if (!normalized) {
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
const countryCode = "62";
|
|
864
|
+
const number = normalized.substring(1);
|
|
865
|
+
const isMobile = normalized.startsWith("08");
|
|
866
|
+
const isLandline = !isMobile;
|
|
867
|
+
let operator = null;
|
|
868
|
+
let region = null;
|
|
869
|
+
if (isMobile) {
|
|
870
|
+
operator = getOperator(normalized);
|
|
871
|
+
} else {
|
|
872
|
+
region = getRegion(normalized);
|
|
873
|
+
}
|
|
874
|
+
return {
|
|
875
|
+
countryCode,
|
|
876
|
+
operator,
|
|
877
|
+
number,
|
|
878
|
+
formatted: {
|
|
879
|
+
international: toInternational(normalized),
|
|
880
|
+
national: toNational(normalized),
|
|
881
|
+
e164: toE164(normalized)
|
|
882
|
+
},
|
|
883
|
+
isValid: true,
|
|
884
|
+
isMobile,
|
|
885
|
+
isLandline,
|
|
886
|
+
region
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
function getOperator(phone) {
|
|
890
|
+
if (!isMobileNumber(phone)) {
|
|
891
|
+
return null;
|
|
892
|
+
}
|
|
893
|
+
const cleaned = cleanPhoneNumber(phone);
|
|
894
|
+
const normalized = normalizeToNational2(cleaned);
|
|
895
|
+
if (!normalized || normalized.length < 4) {
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
const prefix = normalized.substring(0, 4);
|
|
899
|
+
return OPERATOR_PREFIXES[prefix] || null;
|
|
900
|
+
}
|
|
901
|
+
function getRegion(phone) {
|
|
902
|
+
if (!phone.startsWith("0")) {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
const areaCode4 = phone.substring(0, 4);
|
|
906
|
+
if (AREA_CODES[areaCode4]) {
|
|
907
|
+
return AREA_CODES[areaCode4];
|
|
908
|
+
}
|
|
909
|
+
const areaCode3 = phone.substring(0, 3);
|
|
910
|
+
if (AREA_CODES[areaCode3]) {
|
|
911
|
+
return AREA_CODES[areaCode3];
|
|
912
|
+
}
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
function normalizeToNational2(phone) {
|
|
916
|
+
if (phone.startsWith("+62")) {
|
|
917
|
+
return "0" + phone.substring(3);
|
|
918
|
+
} else if (phone.startsWith("62")) {
|
|
919
|
+
return "0" + phone.substring(2);
|
|
920
|
+
} else if (phone.startsWith("0")) {
|
|
921
|
+
return phone;
|
|
922
|
+
}
|
|
923
|
+
return "";
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// src/currency/format.ts
|
|
927
|
+
function formatRupiah(amount, options) {
|
|
928
|
+
const {
|
|
929
|
+
symbol = true,
|
|
930
|
+
decimal = false,
|
|
931
|
+
separator = ".",
|
|
932
|
+
decimalSeparator = ",",
|
|
933
|
+
spaceAfterSymbol = true
|
|
934
|
+
} = options || {};
|
|
935
|
+
const precision = options?.precision !== void 0 ? options.precision : decimal ? 2 : 0;
|
|
936
|
+
const isNegative = amount < 0;
|
|
937
|
+
const absAmount = Math.abs(amount);
|
|
938
|
+
let result;
|
|
939
|
+
if (decimal) {
|
|
940
|
+
const factor = Math.pow(10, precision);
|
|
941
|
+
const rounded = Math.round(absAmount * factor) / factor;
|
|
942
|
+
if (precision > 0) {
|
|
943
|
+
const [intPart, decPart] = rounded.toFixed(precision).split(".");
|
|
944
|
+
const formattedInt = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
|
|
945
|
+
result = `${formattedInt}${decimalSeparator}${decPart}`;
|
|
946
|
+
} else {
|
|
947
|
+
const intPart = rounded.toString();
|
|
948
|
+
result = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
|
|
949
|
+
}
|
|
950
|
+
} else {
|
|
951
|
+
const intAmount = Math.floor(absAmount);
|
|
952
|
+
result = intAmount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator);
|
|
953
|
+
}
|
|
954
|
+
if (isNegative) {
|
|
955
|
+
result = `-${result}`;
|
|
956
|
+
}
|
|
957
|
+
if (symbol) {
|
|
958
|
+
const space = spaceAfterSymbol ? " " : "";
|
|
959
|
+
result = `Rp${space}${result}`;
|
|
960
|
+
}
|
|
961
|
+
return result;
|
|
962
|
+
}
|
|
963
|
+
function formatCompact(amount) {
|
|
964
|
+
const isNegative = amount < 0;
|
|
965
|
+
const abs = Math.abs(amount);
|
|
966
|
+
let result;
|
|
967
|
+
if (abs >= 1e12) {
|
|
968
|
+
result = formatCompactValue(abs / 1e12, "triliun");
|
|
969
|
+
} else if (abs >= 1e9) {
|
|
970
|
+
result = formatCompactValue(abs / 1e9, "miliar");
|
|
971
|
+
} else if (abs >= 1e6) {
|
|
972
|
+
result = formatCompactValue(abs / 1e6, "juta");
|
|
973
|
+
} else if (abs >= 1e5) {
|
|
974
|
+
result = formatCompactValue(abs / 1e3, "ribu");
|
|
975
|
+
} else if (abs >= 1e3) {
|
|
976
|
+
result = abs.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
|
977
|
+
} else {
|
|
978
|
+
result = abs.toString();
|
|
979
|
+
}
|
|
980
|
+
if (isNegative) {
|
|
981
|
+
result = `-${result}`;
|
|
982
|
+
}
|
|
983
|
+
return `Rp ${result}`;
|
|
984
|
+
}
|
|
985
|
+
function formatCompactValue(value, unit) {
|
|
986
|
+
const rounded = Math.round(value * 10) / 10;
|
|
987
|
+
if (rounded % 1 === 0) {
|
|
988
|
+
return `${rounded.toFixed(0)} ${unit}`;
|
|
989
|
+
}
|
|
990
|
+
return `${rounded.toString().replace(".", ",")} ${unit}`;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/currency/parse.ts
|
|
994
|
+
function parseRupiah(formatted) {
|
|
995
|
+
if (!formatted || typeof formatted !== "string") {
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
const cleaned = formatted.trim().toLowerCase();
|
|
999
|
+
const compactUnits = {
|
|
1000
|
+
triliun: 1e12,
|
|
1001
|
+
miliar: 1e9,
|
|
1002
|
+
juta: 1e6,
|
|
1003
|
+
ribu: 1e3
|
|
1004
|
+
};
|
|
1005
|
+
for (const [unit, multiplier] of Object.entries(compactUnits)) {
|
|
1006
|
+
if (cleaned.includes(unit)) {
|
|
1007
|
+
const match = cleaned.match(/(-?\d+[,.]?\d*)/);
|
|
1008
|
+
if (match) {
|
|
1009
|
+
const num = parseFloat(match[1].replace(",", "."));
|
|
1010
|
+
return num * multiplier;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
let numStr = cleaned.replace(/rp/gi, "").trim();
|
|
1015
|
+
const hasDot = numStr.includes(".");
|
|
1016
|
+
const hasComma = numStr.includes(",");
|
|
1017
|
+
if (hasDot && hasComma) {
|
|
1018
|
+
const lastDot = numStr.lastIndexOf(".");
|
|
1019
|
+
const lastComma = numStr.lastIndexOf(",");
|
|
1020
|
+
if (lastComma > lastDot) {
|
|
1021
|
+
numStr = numStr.replace(/\./g, "").replace(",", ".");
|
|
1022
|
+
} else {
|
|
1023
|
+
numStr = numStr.replace(/,/g, "");
|
|
1024
|
+
}
|
|
1025
|
+
} else if (hasComma) {
|
|
1026
|
+
const parts = numStr.split(",");
|
|
1027
|
+
if (parts.length === 2 && parts[1].length <= 2) {
|
|
1028
|
+
numStr = numStr.replace(",", ".");
|
|
1029
|
+
} else {
|
|
1030
|
+
numStr = numStr.replace(/,/g, "");
|
|
1031
|
+
}
|
|
1032
|
+
} else if (hasDot) {
|
|
1033
|
+
const parts = numStr.split(".");
|
|
1034
|
+
if (parts.length > 2 || parts.length === 2 && parts[1].length > 2) {
|
|
1035
|
+
numStr = numStr.replace(/\./g, "");
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
const parsed = parseFloat(numStr);
|
|
1039
|
+
return isNaN(parsed) ? null : parsed;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// src/currency/words.ts
|
|
1043
|
+
var BASIC_NUMBERS = [
|
|
1044
|
+
"",
|
|
1045
|
+
"satu",
|
|
1046
|
+
"dua",
|
|
1047
|
+
"tiga",
|
|
1048
|
+
"empat",
|
|
1049
|
+
"lima",
|
|
1050
|
+
"enam",
|
|
1051
|
+
"tujuh",
|
|
1052
|
+
"delapan",
|
|
1053
|
+
"sembilan"
|
|
1054
|
+
];
|
|
1055
|
+
var TEENS = [
|
|
1056
|
+
"sepuluh",
|
|
1057
|
+
"sebelas",
|
|
1058
|
+
"dua belas",
|
|
1059
|
+
"tiga belas",
|
|
1060
|
+
"empat belas",
|
|
1061
|
+
"lima belas",
|
|
1062
|
+
"enam belas",
|
|
1063
|
+
"tujuh belas",
|
|
1064
|
+
"delapan belas",
|
|
1065
|
+
"sembilan belas"
|
|
1066
|
+
];
|
|
1067
|
+
var TENS = [
|
|
1068
|
+
"",
|
|
1069
|
+
"",
|
|
1070
|
+
"dua puluh",
|
|
1071
|
+
"tiga puluh",
|
|
1072
|
+
"empat puluh",
|
|
1073
|
+
"lima puluh",
|
|
1074
|
+
"enam puluh",
|
|
1075
|
+
"tujuh puluh",
|
|
1076
|
+
"delapan puluh",
|
|
1077
|
+
"sembilan puluh"
|
|
1078
|
+
];
|
|
1079
|
+
function toWords(amount, options) {
|
|
1080
|
+
const { uppercase = false, withCurrency = true } = options || {};
|
|
1081
|
+
if (amount === 0) {
|
|
1082
|
+
let result = "nol";
|
|
1083
|
+
if (withCurrency) result += " rupiah";
|
|
1084
|
+
return uppercase ? capitalize(result) : result;
|
|
1085
|
+
}
|
|
1086
|
+
const isNegative = amount < 0;
|
|
1087
|
+
const absAmount = Math.floor(Math.abs(amount));
|
|
1088
|
+
let words = "";
|
|
1089
|
+
const triliun = Math.floor(absAmount / 1e12);
|
|
1090
|
+
const miliar = Math.floor(absAmount % 1e12 / 1e9);
|
|
1091
|
+
const juta = Math.floor(absAmount % 1e9 / 1e6);
|
|
1092
|
+
const ribu = Math.floor(absAmount % 1e6 / 1e3);
|
|
1093
|
+
const sisa = absAmount % 1e3;
|
|
1094
|
+
if (triliun > 0) {
|
|
1095
|
+
words += convertGroup(triliun) + " triliun";
|
|
1096
|
+
}
|
|
1097
|
+
if (miliar > 0) {
|
|
1098
|
+
if (words) words += " ";
|
|
1099
|
+
words += convertGroup(miliar) + " miliar";
|
|
1100
|
+
}
|
|
1101
|
+
if (juta > 0) {
|
|
1102
|
+
if (words) words += " ";
|
|
1103
|
+
words += convertGroup(juta) + " juta";
|
|
1104
|
+
}
|
|
1105
|
+
if (ribu > 0) {
|
|
1106
|
+
if (words) words += " ";
|
|
1107
|
+
words += ribu === 1 ? "seribu" : convertGroup(ribu) + " ribu";
|
|
1108
|
+
}
|
|
1109
|
+
if (sisa > 0) {
|
|
1110
|
+
if (words) words += " ";
|
|
1111
|
+
words += convertGroup(sisa);
|
|
1112
|
+
}
|
|
1113
|
+
if (isNegative) {
|
|
1114
|
+
words = "minus " + words;
|
|
1115
|
+
}
|
|
1116
|
+
if (withCurrency) {
|
|
1117
|
+
words += " rupiah";
|
|
1118
|
+
}
|
|
1119
|
+
return uppercase ? capitalize(words) : words;
|
|
1120
|
+
}
|
|
1121
|
+
function convertGroup(num) {
|
|
1122
|
+
if (num === 0) return "";
|
|
1123
|
+
let result = "";
|
|
1124
|
+
const hundreds = Math.floor(num / 100);
|
|
1125
|
+
if (hundreds > 0) {
|
|
1126
|
+
result = hundreds === 1 ? "seratus" : BASIC_NUMBERS[hundreds] + " ratus";
|
|
1127
|
+
}
|
|
1128
|
+
const remainder = num % 100;
|
|
1129
|
+
if (remainder > 0) {
|
|
1130
|
+
if (result) result += " ";
|
|
1131
|
+
result += convertTwoDigits(remainder);
|
|
1132
|
+
}
|
|
1133
|
+
return result;
|
|
1134
|
+
}
|
|
1135
|
+
function convertTwoDigits(num) {
|
|
1136
|
+
if (num === 0) return "";
|
|
1137
|
+
if (num < 10) return BASIC_NUMBERS[num];
|
|
1138
|
+
if (num >= 10 && num < 20) return TEENS[num - 10];
|
|
1139
|
+
const tens = Math.floor(num / 10);
|
|
1140
|
+
const ones = num % 10;
|
|
1141
|
+
let result = TENS[tens];
|
|
1142
|
+
if (ones > 0) {
|
|
1143
|
+
result += " " + BASIC_NUMBERS[ones];
|
|
1144
|
+
}
|
|
1145
|
+
return result;
|
|
1146
|
+
}
|
|
1147
|
+
function capitalize(str) {
|
|
1148
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
export { cleanPhoneNumber, formatCompact, formatNIK, formatPhoneNumber, formatRupiah, getOperator, isLandlineNumber, isMobileNumber, maskNIK, maskPhoneNumber, parseNIK, parsePhoneNumber, parseRupiah, toE164, toInternational, toNational, toWords, validateNIK, validatePhoneNumber };
|
|
1152
|
+
//# sourceMappingURL=index.js.map
|
|
1153
|
+
//# sourceMappingURL=index.js.map
|