@quantaroute/checkout 1.1.0 → 1.2.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
@@ -1,7 +1,8 @@
1
1
  # @quantaroute/checkout
2
2
 
3
3
  > **DigiPin-powered smart address checkout widget for Indian e-commerce.**
4
- > Two-step: Map pin → Auto-filled form. Works in any React/Next.js/Nuxt/Vite app.
4
+ > Two-step: Map pin → Auto-filled form.
5
+ > One package. Three platforms: **React web · iOS · Android.**
5
6
 
6
7
  [![Made in India](https://img.shields.io/badge/Made%20in-India%20🇮🇳-orange)](https://quantaroute.com)
7
8
  [![DigiPin](https://img.shields.io/badge/DigiPin-India%20Post-blue)](https://www.indiapost.gov.in)
@@ -12,17 +13,17 @@
12
13
 
13
14
  ## What it does
14
15
 
15
- Replaces your broken Indian address form with a two-step Map-style flow:
16
+ Replaces broken Indian address forms with a precise two-step pin-drop flow:
16
17
 
17
18
  ```
18
19
  Step 1 – Map Pin Step 2 – Auto-fill + Details
19
20
  ┌──────────────────────────┐ ┌──────────────────────────┐
20
21
  │ [DigiPin: 39J-438-TJC7] │ │ 📍 Auto-detected │
21
22
  │ │ │ State: Delhi │
22
- │ 🗺 Carto Positron map │ ──► │ District: New Delhi │
23
+ │ 🗺 OSM vector map │ ──► │ District: New Delhi │
23
24
  │ 📍 ← drag │ │ Pincode: 110011 │
24
25
  │ │ │ ✓ Deliverable │
25
- │ [📡 Locate Me] │ │ 🏠 Add details │
26
+ │ [ Locate Me] │ │ 🏠 Add details │
26
27
  │ [Confirm Location →] │ │ Flat No: [_________] │
27
28
  └──────────────────────────┘ │ [← Adjust] [Save ✓] │
28
29
  └──────────────────────────┘
@@ -30,13 +31,26 @@ Step 1 – Map Pin Step 2 – Auto-fill + Details
30
31
 
31
32
  **Key features:**
32
33
 
33
- - DigiPin shown **offline** in real-time as the user drags the pin (~4m×4m precision)
34
- - **No Google Maps.** Free Carto Positron vector basemap (MapLibre GL JS)
34
+ - DigiPin shown **offline** in real-time as the user drags the pin (~4 m × 4 m precision)
35
+ - **No Google Maps.** Free Carto Positron vector basemap
35
36
  - Auto-fills State, District, Locality, Pincode, Delivery status from QuantaRoute API
36
- - Manual fields: Flat no., Floor, Building (OSM-pre-filled), Street/Area (OSM-pre-filled)
37
- - Mobile-first (full-screen on phones, card on desktop)
37
+ - Manual fields: Flat no., Floor, Building (OSM pre-filled), Street/Area (OSM pre-filled)
38
+ - Mobile-first (full-screen on phones, card on desktop/tablet)
38
39
  - Dark mode, reduced-motion, keyboard navigation, ARIA labels
39
- - TypeScript, zero runtime deps beyond MapLibre + React
40
+ - TypeScript zero runtime deps beyond peer dependencies
41
+
42
+ ---
43
+
44
+ ## Platform support
45
+
46
+ | Platform | Bundler | Map engine | Install |
47
+ |---|---|---|---|
48
+ | React / Next.js / Vite / Nuxt | Webpack / Vite | MapLibre GL JS | `maplibre-gl` |
49
+ | iOS (Expo / React Native) | Metro | expo-osm-sdk (MapLibre GL Native) | `expo-osm-sdk expo-location` |
50
+ | Android (Expo / React Native) | Metro | expo-osm-sdk (MapLibre GL Native) | `expo-osm-sdk expo-location` |
51
+
52
+ > **Same import on all platforms.**
53
+ > Metro automatically resolves `.native.tsx` files on mobile; Vite/Webpack use the web `.tsx` files.
40
54
 
41
55
  ---
42
56
 
@@ -46,36 +60,28 @@ Step 1 – Map Pin Step 2 – Auto-fill + Details
46
60
 
47
61
  1. Sign up at **[developers.quantaroute.com](https://developers.quantaroute.com)**
48
62
  2. Create a project → copy your **API key**
49
- 3. Store it as an environment variable in your app:
50
-
51
- | Framework | Variable name convention |
52
- |-----------|--------------------------|
53
- | Next.js | `NEXT_PUBLIC_QUANTAROUTE_KEY` in `.env.local` |
54
- | Vite | `VITE_QR_API_KEY` in `.env` |
55
- | Nuxt | `NUXT_PUBLIC_QR_API_KEY` in `.env` |
56
- | Node/Express backend | `QUANTAROUTE_KEY` (server-side only) |
57
63
 
58
64
  > **Never hard-code or commit API keys to git.**
59
65
 
60
66
  ---
61
67
 
62
- ### 1 · Install
68
+ ## Web (React / Next.js / Vite / Nuxt)
69
+
70
+ ### Install
63
71
 
64
72
  ```bash
65
73
  npm install @quantaroute/checkout maplibre-gl
66
- # or
67
- yarn add @quantaroute/checkout maplibre-gl
68
74
  ```
69
75
 
70
- ### 2 · Import CSS
76
+ ### Import CSS
71
77
 
72
78
  ```ts
73
- // In your app's entry file (main.tsx / _app.tsx / nuxt.config.ts, etc.)
79
+ // In your app entry file (main.tsx / _app.tsx / layout.tsx / nuxt.config.ts)
74
80
  import 'maplibre-gl/dist/maplibre-gl.css';
75
- import '@quantaroute/checkout/dist/style.css';
81
+ import '@quantaroute/checkout/style.css';
76
82
  ```
77
83
 
78
- ### 3 · Drop it in
84
+ ### Use
79
85
 
80
86
  ```tsx
81
87
  import { CheckoutWidget } from '@quantaroute/checkout';
@@ -83,11 +89,11 @@ import { CheckoutWidget } from '@quantaroute/checkout';
83
89
  export default function CheckoutPage() {
84
90
  return (
85
91
  <CheckoutWidget
86
- apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!} {/* never hard-code keys */}
92
+ apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
87
93
  onComplete={(address) => {
88
- console.log(address.digipin); // "39J-438-TJC7"
89
- console.log(address.pincode); // "110011"
90
- console.log(address.formattedAddress); // "Flat 4B, Floor 3rd, ..."
94
+ console.log(address.digipin); // "39J-438-TJC7"
95
+ console.log(address.pincode); // "110011"
96
+ console.log(address.formattedAddress); // "Flat 4B, Floor 3rd, ..."
91
97
  // → send to your backend / payment gateway
92
98
  }}
93
99
  />
@@ -95,80 +101,24 @@ export default function CheckoutPage() {
95
101
  }
96
102
  ```
97
103
 
98
- > **Get your API key** (free tier: 25,000 requests/month) →
99
- > **[developers.quantaroute.com](https://developers.quantaroute.com)**
100
- >
101
- > Store it as an environment variable — **never commit API keys to source control**.
102
-
103
- ---
104
-
105
- ## Props
106
-
107
- | Prop | Type | Default | Description |
108
- |------|------|---------|-------------|
109
- | `apiKey` | `string` | **required** | QuantaRoute API key |
110
- | `onComplete` | `(addr: CompleteAddress) => void` | **required** | Fired after user saves address |
111
- | `apiBaseUrl` | `string` | `https://api.quantaroute.com` | Custom API URL (self-hosted) |
112
- | `onError` | `(err: Error) => void` | – | Optional error handler |
113
- | `defaultLat` | `number` | India center | Pre-center the map here |
114
- | `defaultLng` | `number` | India center | Pre-center the map here |
115
- | `theme` | `'light' \| 'dark'` | `'light'` | Color theme |
116
- | `mapHeight` | `string` | `'380px'` | CSS height of the map area |
117
- | `title` | `string` | `'Add Delivery Address'` | Widget header text |
118
- | `className` | `string` | – | Extra class on root element |
119
- | `style` | `CSSProperties` | – | Inline styles on root element |
120
-
121
- ---
122
-
123
- ## `CompleteAddress` output
124
-
125
- ```ts
126
- interface CompleteAddress {
127
- digipin: string; // "39J-438-TJC7"
128
- lat: number; // 28.61390
129
- lng: number; // 77.20900
130
-
131
- // Auto-filled from QuantaRoute API
132
- state: string; // "Delhi"
133
- district: string; // "New Delhi"
134
- division: string; // "New Delhi Central"
135
- locality: string; // "Nirman Bhawan SO"
136
- pincode: string; // "110011"
137
- delivery: string; // "Delivery" | "Non Delivery"
138
- country: string; // "India"
139
-
140
- // Manual entry by user
141
- flatNumber: string; // "4B"
142
- floorNumber: string; // "3rd"
143
- buildingName: string; // "Sunshine Apartments" (pre-filled from Nominatim OSM)
144
- streetName: string; // "MG Road, Action Area I" (pre-filled from road + suburb)
145
-
146
- formattedAddress: string; // "4B, 3rd Floor, Sunshine Apartments, MG Road, ..."
147
- }
148
- ```
149
-
150
- ---
151
-
152
- ## Framework integration guides
153
-
154
104
  ### Next.js (App Router)
155
105
 
156
106
  ```tsx
157
107
  // app/checkout/page.tsx
158
- 'use client'; // ← required (MapLibre is browser-only)
108
+ 'use client';
159
109
 
160
110
  import dynamic from 'next/dynamic';
161
111
 
162
112
  const CheckoutWidget = dynamic(
163
113
  () => import('@quantaroute/checkout').then((m) => m.CheckoutWidget),
164
- { ssr: false } // ← required (no SSR for map)
114
+ { ssr: false }
165
115
  );
166
116
 
167
117
  export default function CheckoutPage() {
168
118
  return (
169
119
  <main className="max-w-lg mx-auto p-4">
170
120
  <CheckoutWidget
171
- apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!} {/* set in .env.local */}
121
+ apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
172
122
  onComplete={(addr) => console.log(addr)}
173
123
  />
174
124
  </main>
@@ -176,172 +126,317 @@ export default function CheckoutPage() {
176
126
  }
177
127
  ```
178
128
 
179
- Add CSS imports to `app/layout.tsx`:
129
+ CSS in `app/layout.tsx`:
180
130
  ```tsx
181
131
  import 'maplibre-gl/dist/maplibre-gl.css';
182
- import '@quantaroute/checkout/dist/style.css';
132
+ import '@quantaroute/checkout/style.css';
183
133
  ```
184
134
 
185
- ---
186
-
187
135
  ### Next.js (Pages Router)
188
136
 
189
137
  ```tsx
190
- // pages/checkout.tsx
191
138
  import dynamic from 'next/dynamic';
192
139
  const CheckoutWidget = dynamic(() => import('@quantaroute/checkout'), { ssr: false });
193
140
 
194
141
  export default function CheckoutPage() {
195
142
  return (
196
143
  <CheckoutWidget
197
- apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!} {/* .env.local */}
144
+ apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
198
145
  onComplete={(a) => console.log(a)}
199
146
  />
200
147
  );
201
148
  }
202
149
  ```
203
150
 
204
- ---
205
-
206
151
  ### Nuxt 3
207
152
 
208
153
  ```ts
209
154
  // nuxt.config.ts
210
155
  export default defineNuxtConfig({
211
- css: [
212
- 'maplibre-gl/dist/maplibre-gl.css',
213
- '@quantaroute/checkout/dist/style.css',
214
- ],
156
+ css: ['maplibre-gl/dist/maplibre-gl.css', '@quantaroute/checkout/style.css'],
215
157
  });
216
158
  ```
217
159
 
218
160
  ```vue
219
- <!-- components/AddressWidget.client.vue -->
220
- <!-- The .client suffix ensures this only renders on the client -->
161
+ <!-- components/AddressWidget.client.vue (.client = browser-only) -->
221
162
  <script setup lang="ts">
222
163
  import { CheckoutWidget } from '@quantaroute/checkout';
223
-
224
- function handleComplete(address) {
225
- console.log(address.digipin, address.formattedAddress);
226
- }
164
+ const { public: { qrApiKey } } = useRuntimeConfig();
227
165
  </script>
228
-
229
166
  <template>
230
- <!-- API key via runtimeConfig → useRuntimeConfig().public.qrApiKey -->
231
- <CheckoutWidget
232
- :api-key="runtimeConfig.public.qrApiKey"
233
- map-height="360px"
234
- @complete="handleComplete"
235
- />
167
+ <CheckoutWidget :api-key="qrApiKey" map-height="360px" @complete="console.log" />
236
168
  </template>
237
169
  ```
238
170
 
239
- ---
240
-
241
171
  ### Vite + React
242
172
 
243
173
  ```tsx
244
- // src/App.tsx
245
174
  import 'maplibre-gl/dist/maplibre-gl.css';
246
- import '@quantaroute/checkout/dist/style.css';
175
+ import '@quantaroute/checkout/style.css';
247
176
  import { CheckoutWidget } from '@quantaroute/checkout';
248
177
 
249
178
  function App() {
250
179
  return (
251
- <div style={{ padding: '20px' }}>
252
- <CheckoutWidget
253
- apiKey={import.meta.env.VITE_QR_API_KEY}
254
- onComplete={(addr) => console.log('Saved:', addr)}
255
- />
256
- </div>
180
+ <CheckoutWidget
181
+ apiKey={import.meta.env.VITE_QR_API_KEY}
182
+ onComplete={(addr) => console.log('Saved:', addr)}
183
+ />
257
184
  );
258
185
  }
259
186
  ```
260
187
 
188
+ ### Vanilla JS (UMD / script tag)
189
+
190
+ ```html
191
+ <link rel="stylesheet" href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" />
192
+ <link rel="stylesheet" href="https://unpkg.com/@quantaroute/checkout/dist/style.css" />
193
+
194
+ <div id="checkout-root"></div>
195
+
196
+ <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
197
+ <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
198
+ <script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
199
+ <script src="https://unpkg.com/@quantaroute/checkout/dist/lib/quantaroute-checkout.umd.js"></script>
200
+ <script>
201
+ ReactDOM.createRoot(document.getElementById('checkout-root')).render(
202
+ React.createElement(QuantaRouteCheckout.CheckoutWidget, {
203
+ apiKey: 'YOUR_KEY',
204
+ onComplete: (addr) => console.log('Done:', addr),
205
+ })
206
+ );
207
+ </script>
208
+ ```
209
+
261
210
  ---
262
211
 
263
- ### Vanilla JS (UMD / Script tag)
212
+ ## Native (Expo / React Native — iOS & Android)
264
213
 
265
- ```html
266
- <!DOCTYPE html>
267
- <html>
268
- <head>
269
- <link rel="stylesheet" href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" />
270
- <link rel="stylesheet" href="https://unpkg.com/@quantaroute/checkout/dist/style.css" />
271
- </head>
272
- <body>
273
- <div id="checkout-root"></div>
274
-
275
- <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
276
- <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
277
- <script src="https://unpkg.com/@quantaroute/checkout/dist/lib/quantaroute-checkout.umd.js"></script>
278
- <script>
279
- const { createRoot } = ReactDOM;
280
- const { CheckoutWidget } = QuantaRouteCheckout;
281
-
282
- // Replace window.__QR_API_KEY__ with your key from developers.quantaroute.com
283
- // In production, inject this via server-side template or env variable.
284
- createRoot(document.getElementById('checkout-root')).render(
285
- React.createElement(CheckoutWidget, {
286
- apiKey: window.__QR_API_KEY__ || '',
287
- onComplete: (addr) => console.log('Done:', addr),
288
- })
289
- );
290
- </script>
291
- </body>
292
- </html>
214
+ > **Requires a [development build](https://docs.expo.dev/develop/development-builds/introduction/).**
215
+ > This does NOT work in Expo Go — `expo-osm-sdk` uses native modules.
216
+
217
+ ### 1 · Install
218
+
219
+ ```bash
220
+ npm install @quantaroute/checkout expo-osm-sdk expo-location
221
+ ```
222
+
223
+ ### 2 · Add the config plugin to `app.json`
224
+
225
+ ```json
226
+ {
227
+ "expo": {
228
+ "plugins": [
229
+ ["@quantaroute/checkout/plugin", {
230
+ "locationPermissionText": "Allow access to your location to place the delivery pin on the map."
231
+ }]
232
+ ]
233
+ }
234
+ }
235
+ ```
236
+
237
+ This single plugin automatically configures:
238
+
239
+ | What | iOS | Android |
240
+ |---|---|---|
241
+ | MapLibre native SDK | `expo-osm-sdk/plugin` | `expo-osm-sdk/plugin` |
242
+ | Location permission | `NSLocationWhenInUseUsageDescription` | `ACCESS_FINE_LOCATION` |
243
+ | Internet permission | – | `INTERNET` |
244
+
245
+ ### 3 · Rebuild your dev client
246
+
247
+ ```bash
248
+ npx expo run:ios
249
+ # or
250
+ npx expo run:android
251
+ ```
252
+
253
+ ### 4 · Use — identical import as web
254
+
255
+ ```tsx
256
+ import { CheckoutWidget } from '@quantaroute/checkout';
257
+ // No CSS import — styles are React Native StyleSheet objects
258
+
259
+ export default function CheckoutScreen() {
260
+ return (
261
+ <CheckoutWidget
262
+ apiKey={process.env.EXPO_PUBLIC_QUANTAROUTE_KEY!}
263
+ mapHeight={380}
264
+ onComplete={(address) => {
265
+ console.log(address.digipin); // "39J-438-TJC7"
266
+ console.log(address.formattedAddress); // "Flat 4B, ..."
267
+ }}
268
+ />
269
+ );
270
+ }
271
+ ```
272
+
273
+ ### Demo native app
274
+
275
+ A working Expo demo is in `demo-native/`:
276
+
277
+ ```bash
278
+ cd demo-native
279
+ npm install
280
+ # Set your API key in .env: EXPO_PUBLIC_QUANTAROUTE_API_KEY=your_key
281
+ npx expo run:ios
282
+ npx expo run:android
293
283
  ```
294
284
 
295
285
  ---
296
286
 
297
- ### React Native (coming soon)
287
+ ## Props
288
+
289
+ | Prop | Type | Default | Platform |
290
+ |---|---|---|---|
291
+ | `apiKey` | `string` | **required** | all |
292
+ | `onComplete` | `(addr: CompleteAddress) => void` | **required** | all |
293
+ | `apiBaseUrl` | `string` | `https://api.quantaroute.com` | all |
294
+ | `onError` | `(err: Error) => void` | – | all |
295
+ | `defaultLat` | `number` | India center | all |
296
+ | `defaultLng` | `number` | India center | all |
297
+ | `theme` | `'light' \| 'dark'` | `'light'` | all |
298
+ | `mapHeight` | `string \| number` | `'380px'` / `380` | all |
299
+ | `title` | `string` | `'Add Delivery Address'` | all |
300
+ | `className` | `string` | – | web only |
301
+ | `style` | `CSSProperties \| StyleProp<ViewStyle>` | – | all |
302
+ | `indiaBoundaryUrl` | `string` | – | web only |
303
+
304
+ ---
305
+
306
+ ## `CompleteAddress` output
307
+
308
+ ```ts
309
+ interface CompleteAddress {
310
+ digipin: string; // "39J-438-TJC7"
311
+ lat: number; // 28.61390
312
+ lng: number; // 77.20900
313
+
314
+ // Auto-filled from QuantaRoute API
315
+ state: string; // "Delhi"
316
+ district: string; // "New Delhi"
317
+ division: string; // "New Delhi Central"
318
+ locality: string; // "Nirman Bhawan SO"
319
+ pincode: string; // "110011"
320
+ delivery: string; // "Delivery" | "Non Delivery"
321
+ country: string; // "India"
322
+
323
+ // Manual entry by user
324
+ flatNumber: string; // "4B"
325
+ floorNumber: string; // "3rd"
326
+ buildingName: string; // "Sunshine Apartments" (OSM pre-filled)
327
+ streetName: string; // "MG Road, Action Area" (OSM pre-filled)
298
328
 
299
- A React Native version is planned that will use `react-native-maps` + the same offline DigiPin algorithm and QuantaRoute API. Track progress in issues.
329
+ formattedAddress: string; // "4B, 3rd Floor, Sunshine Apartments, MG Road, ..."
330
+ }
331
+ ```
300
332
 
301
333
  ---
302
334
 
303
335
  ## Advanced usage
304
336
 
305
- ### Use sub-components individually
337
+ ### Use sub-components individually (web + native)
306
338
 
307
339
  ```tsx
308
340
  import { MapPinSelector, AddressForm, getDigiPin, isWithinIndia } from '@quantaroute/checkout';
309
341
 
310
- // Offline DigiPin (no API call needed)
342
+ // Offline DigiPin no API call, ~0.1 ms
311
343
  const dp = getDigiPin(28.6139, 77.2090); // "39J-438-TJC7"
312
- const valid = isWithinIndia(28.6139, 77.2090); // true
344
+ const ok = isWithinIndia(28.6139, 77.2090); // true
313
345
 
314
346
  // Custom two-step flow
315
347
  function MyCheckout() {
316
- const [loc, setLoc] = useState(null);
348
+ const [loc, setLoc] = useState<{ lat: number; lng: number; digipin: string } | null>(null);
317
349
 
318
350
  return loc == null
319
- ? <MapPinSelector onLocationConfirm={(lat, lng, dp) => setLoc({ lat, lng, dp })} />
320
- : <AddressForm {...loc} digipin={loc.dp} apiKey="..." onAddressComplete={...} onBack={...} />;
351
+ ? <MapPinSelector onLocationConfirm={(lat, lng, digipin) => setLoc({ lat, lng, digipin })} />
352
+ : <AddressForm
353
+ lat={loc.lat}
354
+ lng={loc.lng}
355
+ digipin={loc.digipin}
356
+ apiKey="..."
357
+ onAddressComplete={(addr) => console.log(addr)}
358
+ onBack={() => setLoc(null)}
359
+ />;
321
360
  }
322
361
  ```
323
362
 
324
- ### Custom theme with CSS variables
363
+ ### Dark mode (web)
364
+
365
+ ```tsx
366
+ <CheckoutWidget apiKey="..." theme="dark" onComplete={...} />
367
+ ```
368
+
369
+ ### Custom web theme via CSS variables
325
370
 
326
371
  ```css
327
- /* Override the design tokens in your global CSS */
328
372
  .qr-checkout {
329
- --qr-primary: #6366f1; /* indigo */
373
+ --qr-primary: #6366f1;
330
374
  --qr-primary-dark: #4f46e5;
331
375
  --qr-radius: 8px;
332
376
  --qr-font: 'Poppins', sans-serif;
333
377
  }
334
378
  ```
335
379
 
380
+ ### India boundary overlay (web only)
381
+
382
+ ```tsx
383
+ <CheckoutWidget
384
+ apiKey="..."
385
+ indiaBoundaryUrl="/geojson/india.geojson" {/* hosted in your public folder */}
386
+ onComplete={...}
387
+ />
388
+ ```
389
+
390
+ ---
391
+
392
+ ## Architecture
393
+
394
+ ```
395
+ @quantaroute/checkout/
396
+ ├── src/
397
+ │ ├── components/
398
+ │ │ ├── CheckoutWidget.tsx ← web (MapLibre GL JS)
399
+ │ │ ├── CheckoutWidget.native.tsx ← native (SafeAreaView)
400
+ │ │ ├── MapPinSelector.tsx ← web (MapLibre marker)
401
+ │ │ ├── MapPinSelector.native.tsx ← native (expo-osm-sdk OSMView)
402
+ │ │ ├── AddressForm.tsx ← web (HTML form)
403
+ │ │ └── AddressForm.native.tsx ← native (TextInput / Modal)
404
+ │ ├── core/
405
+ │ │ ├── digipin.ts ← offline DigiPin algorithm (shared, no DOM)
406
+ │ │ ├── api.ts ← QuantaRoute API client (shared, fetch)
407
+ │ │ └── types.ts ← TypeScript types (shared)
408
+ │ ├── hooks/
409
+ │ │ ├── useGeolocation.ts ← web (navigator.geolocation)
410
+ │ │ ├── useGeolocation.native.ts ← native (expo-location)
411
+ │ │ └── useDigiPin.ts ← shared (pure math)
412
+ │ └── styles/
413
+ │ ├── checkout.css ← web styles
414
+ │ └── checkout.native.ts ← native StyleSheet.create()
415
+ ├── expo-plugin.js ← Expo config plugin
416
+ ├── babel.config.js ← Metro/Babel config
417
+ └── dist/ ← web build output (Vite)
418
+ ```
419
+
420
+ **Platform resolution:**
421
+
422
+ ```
423
+ Metro (Expo app) → "react-native" export → src/index.ts
424
+ → MapPinSelector.native.tsx (expo-osm-sdk)
425
+ → useGeolocation.native.ts (expo-location)
426
+
427
+ Vite / Webpack / Next.js → "import" export → dist/lib/quantaroute-checkout.es.js
428
+ → MapPinSelector.tsx (MapLibre GL JS)
429
+ → useGeolocation.ts (navigator.geolocation)
430
+ ```
431
+
336
432
  ---
337
433
 
338
434
  ## Map tile license
339
435
 
340
- This widget uses **Carto Positron** vector tiles via MapLibre GL JS.
436
+ Uses **Carto Positron** vector tiles (Carto Voyager on native):
341
437
 
342
- - Free for commercial use (attribution required)
438
+ - Free for commercial use attribution required
343
439
  - Attribution: `© OpenStreetMap contributors © CARTO`
344
- - Does **not** use Google Maps, Mapbox, or OSM raster tiles
345
440
  - No API key required for Carto basemaps
346
441
 
347
442
  ---
@@ -361,28 +456,60 @@ The offline DigiPin algorithm is the official India Post implementation:
361
456
  ```bash
362
457
  git clone https://github.com/quantaroute/checkout.git
363
458
  cd quantaroute-checkout
364
-
365
459
  npm install
366
- npm run dev # Dev server at http://localhost:5173
367
460
 
368
- npm run type-check # TypeScript check
369
- npm run build # Build demo
370
- npm run build:lib # Build library (dist/lib/)
461
+ # Web dev server
462
+ npm run dev # http://localhost:5173
463
+
464
+ # Type checks
465
+ npm run type-check # web (Vite tsconfig)
466
+ npm run type-check:native # native (RN tsconfig)
467
+
468
+ # Build
469
+ npm run build:lib # library → dist/lib/
470
+ npm run build # library + demo
471
+ ```
472
+
473
+ **Native demo:**
474
+
475
+ ```bash
476
+ cd demo-native
477
+ npm install
478
+ npx expo run:ios # requires Xcode
479
+ npx expo run:android # requires Android Studio
371
480
  ```
372
481
 
373
482
  ---
374
483
 
375
484
  ## Changelog
376
485
 
486
+ ### v1.2.0
487
+
488
+ - **iOS & Android support** via `expo-osm-sdk` (MapLibre GL Native)
489
+ - `MapPinSelector.native.tsx` — draggable OSMView pin
490
+ - `AddressForm.native.tsx` — `TextInput` / `ScrollView` / `Modal` locality picker
491
+ - `CheckoutWidget.native.tsx` — `SafeAreaView` layout with identical flow logic
492
+ - `useGeolocation.native.ts` — GPS via `expo-location`
493
+ - `checkout.native.ts` — complete `StyleSheet` design system (light + dark)
494
+ - `expo-plugin.js` — one-line `app.json` setup for iOS/Android
495
+ - `babel.config.js` — Metro transpilation config
496
+ - `mapHeight` prop now accepts `number` (native) or `string` (web, e.g. `'380px'`)
497
+ - All native peer dependencies marked optional (zero impact on web bundles)
498
+
499
+ ### v1.1.x
500
+
501
+ - DigiPin badge overlay on map
502
+ - Locality alternatives dropdown
503
+ - OSM address pre-fill (building name, road, suburb)
504
+ - India boundary GeoJSON overlay
505
+
377
506
  ### v1.0.0
378
507
 
379
- - Initial release
380
- - MapLibre GL + Carto Positron (free, no API key)
381
- - Offline DigiPin generation (real-time on pin drag)
382
- - QuantaRoute location lookup API integration
508
+ - Initial release: MapLibre GL + Carto Positron
509
+ - Offline DigiPin generation
510
+ - QuantaRoute API integration
383
511
  - Mobile-first responsive design
384
512
  - Dark mode, reduced-motion, keyboard accessible
385
- - React / Next.js / Nuxt / Vite / Vanilla JS support
386
513
 
387
514
  ---
388
515
 
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Babel configuration for React Native / Expo consumers.
3
+ *
4
+ * This file is picked up automatically by Metro bundler when the package
5
+ * is used inside an Expo / React Native app. It transpiles the TypeScript
6
+ * source (src/) that Metro reads via the "react-native" export condition.
7
+ *
8
+ * Web builds continue to use Vite (which handles its own transpilation).
9
+ */
10
+ module.exports = {
11
+ presets: [
12
+ ['@babel/preset-env', { targets: { node: 'current' } }],
13
+ ['@babel/preset-react', { runtime: 'automatic' }],
14
+ '@babel/preset-typescript',
15
+ ],
16
+ };