geo-sl 1.0.2 → 1.1.0

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/README.md CHANGED
@@ -126,13 +126,17 @@ import { validateNIC, parseNIC, convertOldNICToNew, validatePhone, parsePhone, f
126
126
 
127
127
  ---
128
128
 
129
- ## 🎨 Interactive React / Next.js Cascading Form Example
129
+ ## 🎨 Interactive React / Next.js Cascading Form Examples
130
130
 
131
- The easiest way to build a Sri Lankan checkout address form:
131
+ `geo-sl` makes building multi-level dependent dropdowns straightforward for both e-commerce checkouts and official KYC/government forms.
132
+
133
+ ### 1. Delivery & Checkout Address Form (`Province ➔ District ➔ City / Postal Code`)
134
+
135
+ For shipping and delivery addresses, use `getCascadingData()` or `toSelectOptions()`:
132
136
 
133
137
  ```tsx
134
138
  import React, { useState } from 'react';
135
- import { getCascadingData } from 'geo-sl';
139
+ import { getCascadingData, toSelectOptions } from 'geo-sl';
136
140
 
137
141
  const addressData = getCascadingData({ lang: 'en' });
138
142
 
@@ -144,22 +148,32 @@ export function SriLankaAddressForm() {
144
148
  const currentProvince = addressData.find((p) => p.code === selectedProvince);
145
149
  const currentDistrict = currentProvince?.districts.find((d) => d.code === selectedDistrict);
146
150
 
151
+ // Convert to standard { label, value } options for React-Select / Shadcn / HTML select
152
+ const provinceOptions = toSelectOptions(addressData, 'name', 'code');
153
+ const districtOptions = toSelectOptions(currentProvince?.districts || [], 'name', 'code');
154
+ const cityOptions = toSelectOptions(
155
+ currentDistrict?.cities || [],
156
+ (c) => `${c.name} (${c.postal_code})`,
157
+ 'name'
158
+ );
159
+
147
160
  return (
148
161
  <div className="space-y-4">
149
162
  {/* Province */}
150
163
  <select
151
164
  value={selectedProvince}
152
165
  onChange={(e) => {
153
- setSelectedProvince(e.target.value as any);
154
- const prov = addressData.find((p) => p.code === e.target.value);
166
+ const code = e.target.value;
167
+ setSelectedProvince(code as any);
168
+ const prov = addressData.find((p) => p.code === code);
155
169
  if (prov && prov.districts[0]) {
156
170
  setSelectedDistrict(prov.districts[0].code);
157
171
  setSelectedCity(prov.districts[0].cities[0]?.name || '');
158
172
  }
159
173
  }}
160
174
  >
161
- {addressData.map((p) => (
162
- <option key={p.code} value={p.code}>{p.name}</option>
175
+ {provinceOptions.map((opt) => (
176
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
163
177
  ))}
164
178
  </select>
165
179
 
@@ -167,15 +181,16 @@ export function SriLankaAddressForm() {
167
181
  <select
168
182
  value={selectedDistrict}
169
183
  onChange={(e) => {
170
- setSelectedDistrict(e.target.value as any);
171
- const dist = currentProvince?.districts.find((d) => d.code === e.target.value);
184
+ const code = e.target.value;
185
+ setSelectedDistrict(code as any);
186
+ const dist = currentProvince?.districts.find((d) => d.code === code);
172
187
  if (dist && dist.cities[0]) {
173
188
  setSelectedCity(dist.cities[0].name);
174
189
  }
175
190
  }}
176
191
  >
177
- {currentProvince?.districts.map((d) => (
178
- <option key={d.code} value={d.code}>{d.name}</option>
192
+ {districtOptions.map((opt) => (
193
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
179
194
  ))}
180
195
  </select>
181
196
 
@@ -184,10 +199,8 @@ export function SriLankaAddressForm() {
184
199
  value={selectedCity}
185
200
  onChange={(e) => setSelectedCity(e.target.value)}
186
201
  >
187
- {currentDistrict?.cities.map((c) => (
188
- <option key={c.name} value={c.name}>
189
- {c.name} ({c.postal_code})
190
- </option>
202
+ {cityOptions.map((opt) => (
203
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
191
204
  ))}
192
205
  </select>
193
206
  </div>
@@ -195,6 +208,94 @@ export function SriLankaAddressForm() {
195
208
  }
196
209
  ```
197
210
 
211
+ ---
212
+
213
+ ### 2. Administrative & Village Form (`Province ➔ District ➔ DS Division ➔ Village / GN`)
214
+
215
+ For banking KYC, voter registration, legal documentation, or government administrative forms, query on-demand down to the **14,020 official villages (Grama Niladhari divisions)**:
216
+
217
+ ```tsx
218
+ import React, { useState, useEffect } from 'react';
219
+ import { getProvinces, getDistrictsByProvince, getDivisionsByDistrict, toSelectOptions } from 'geo-sl';
220
+ import { getVillagesByDivision, searchVillages, type Village } from 'geo-sl/gn';
221
+
222
+ export function SriLankaAdministrativeForm() {
223
+ const [provinceCode, setProvinceCode] = useState('WP');
224
+ const [district, setDistrict] = useState('Colombo');
225
+ const [division, setDivision] = useState('Colombo');
226
+ const [villages, setVillages] = useState<readonly Village[]>([]);
227
+ const [selectedVillage, setSelectedVillage] = useState('');
228
+
229
+ // Load villages dynamically whenever the DS division changes
230
+ useEffect(() => {
231
+ const list = getVillagesByDivision(division);
232
+ setVillages(list);
233
+ if (list.length > 0) setSelectedVillage(list[0].code);
234
+ }, [division]);
235
+
236
+ const provinces = toSelectOptions(getProvinces(), 'name', 'code');
237
+ const districts = toSelectOptions(getDistrictsByProvince(provinceCode), 'name', 'name_en');
238
+ const divisions = toSelectOptions(getDivisionsByDistrict(district), 'name', 'name_en');
239
+ const villageOptions = toSelectOptions(
240
+ villages,
241
+ (v) => `${v.name} (${v.code})`,
242
+ 'code'
243
+ );
244
+
245
+ return (
246
+ <form className="space-y-4">
247
+ {/* 1. Province */}
248
+ <select
249
+ value={provinceCode}
250
+ onChange={(e) => {
251
+ setProvinceCode(e.target.value);
252
+ const firstDist = getDistrictsByProvince(e.target.value)[0];
253
+ if (firstDist) {
254
+ setDistrict(firstDist.name_en);
255
+ const firstDiv = getDivisionsByDistrict(firstDist.name_en)[0];
256
+ if (firstDiv) setDivision(firstDiv.name_en);
257
+ }
258
+ }}
259
+ >
260
+ {provinces.map((p) => <option key={p.value} value={p.value}>{p.label}</option>)}
261
+ </select>
262
+
263
+ {/* 2. District */}
264
+ <select
265
+ value={district}
266
+ onChange={(e) => {
267
+ setDistrict(e.target.value);
268
+ const firstDiv = getDivisionsByDistrict(e.target.value)[0];
269
+ if (firstDiv) setDivision(firstDiv.name_en);
270
+ }}
271
+ >
272
+ {districts.map((d) => <option key={d.value} value={d.value}>{d.label}</option>)}
273
+ </select>
274
+
275
+ {/* 3. DS Division */}
276
+ <select
277
+ value={division}
278
+ onChange={(e) => setDivision(e.target.value)}
279
+ >
280
+ {divisions.map((div) => <option key={div.value} value={div.value}>{div.label}</option>)}
281
+ </select>
282
+
283
+ {/* 4. Village (Grama Niladhari Division) */}
284
+ <select
285
+ value={selectedVillage}
286
+ onChange={(e) => setSelectedVillage(e.target.value)}
287
+ >
288
+ {villageOptions.map((v) => <option key={v.value} value={v.value}>{v.label}</option>)}
289
+ </select>
290
+ </form>
291
+ );
292
+ }
293
+ ```
294
+
295
+ > [!TIP]
296
+ > **Autocomplete for Villages:** With 14,000+ villages, you can also use `searchVillages('Mattakkuliya', { district: 'Colombo', limit: 10 })` to power modern searchable comboboxes (Shadcn, Headless UI, Radix) across English, Sinhala (`මට්ටක්කුලිය`), and Tamil (`மட்டக்குளி`).
297
+
298
+
198
299
  ---
199
300
 
200
301
  ## 🏦 CBSL Bank & Branch Codes (`geo-sl/banks`)
@@ -244,8 +345,10 @@ const branches = getBranches('7010');
244
345
  * `lookupAllByPostalCode(code: string | number, options?: { lang?: 'en' | 'si' | 'ta' }): City[]` – returns all offices sharing a postal code.
245
346
  * `search(query: string, options?: { limit?: number; lang?: 'en' | 'si' | 'ta'; district?: string; province?: string }): City[]`
246
347
 
247
- ### Cascading Hierarchy
248
- * `getCascadingData(options?: { lang?: 'en' | 'si' | 'ta' }): CascadingProvince[]`
348
+ ### Cascading Hierarchy & Form Helpers
349
+ * `getCascadingData(options?: { lang?: 'en' | 'si' | 'ta' }): CascadingProvince[]` – pre-nested tree (Province ➔ District ➔ Cities).
350
+ * `getAdministrativeCascadingData(options?: { lang?: 'en' | 'si' | 'ta' }): CascadingAdministrativeProvince[]` – pre-nested tree (Province ➔ District ➔ DS Divisions).
351
+ * `toSelectOptions<T>(items, labelKey, valueKey): SelectOption[]` – universal formatter converting data objects into `{ label, value }` pairs for UI dropdowns.
249
352
 
250
353
  ### Administrative Divisions (MOHA)
251
354
  * `getDivisions(district?: string, options?: { lang?: 'en' | 'si' | 'ta' }): Division[]`
@@ -260,12 +363,19 @@ const branches = getBranches('7010');
260
363
  * `getBranchByCode(bankCode: string | number, branchCode: string | number): Branch | undefined`
261
364
  * `searchBranches(bankCode: string | number, query: string): Branch[]`
262
365
 
263
- ### Grama Niladhari (GN) Divisions (`geo-sl/gn`)
366
+ ### Grama Niladhari (GN) & Village Divisions (`geo-sl/gn`)
264
367
  * `getGNDivisions(options?: { lang?: 'en' | 'si' | 'ta' }): GNDivision[]`
265
368
  * `getGNDivisionsByDSD(divisionName: string, options?: { lang?: 'en' | 'si' | 'ta' }): GNDivision[]`
266
369
  * `getGNDivisionsByDistrict(districtName: string, options?: { lang?: 'en' | 'si' | 'ta' }): GNDivision[]`
267
370
  * `findGNByCode(code: string, options?: { lang?: 'en' | 'si' | 'ta' }): GNDivision | undefined`
268
371
  * `searchGN(query: string, options?: { limit?: number; lang?: 'en' | 'si' | 'ta'; district?: string; division?: string }): GNDivision[]`
372
+ * **Village Aliases:**
373
+ * `getVillages(options?)` – alias for `getGNDivisions`
374
+ * `getVillagesByDivision(divisionName, options?)` – alias for `getGNDivisionsByDSD`
375
+ * `getVillagesByDistrict(districtName, options?)` – alias for `getGNDivisionsByDistrict`
376
+ * `findVillageByCode(code, options?)` – alias for `findGNByCode`
377
+ * `searchVillages(query, options?)` – alias for `searchGN`
378
+ * `VILLAGES` – alias for `GN_DIVISIONS`
269
379
 
270
380
  ### Sri Lanka Validators & Parsers (`geo-sl/validators`)
271
381
  * `validateNIC(nic: string): boolean`
@@ -287,15 +397,21 @@ import type {
287
397
  Bank,
288
398
  Branch,
289
399
  GNDivision,
400
+ Village,
401
+ SelectOption,
290
402
  CascadingProvince,
291
403
  CascadingDistrict,
404
+ CascadingAdministrativeProvince,
405
+ CascadingAdministrativeDistrict,
406
+ CascadingAdministrativeDivision,
292
407
  ParsedNIC,
293
408
  ParsedPhone,
294
409
  Language,
295
410
  ProvinceCode,
296
411
  DistrictCode,
297
412
  QueryOptions,
298
- SearchOptions
413
+ SearchOptions,
414
+ VillageSearchOptions
299
415
  } from 'geo-sl';
300
416
  ```
301
417