@placeslayer/sdk-react-native 1.0.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/dist/index.cjs +357 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +68 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +335 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
CitiesDropdown: () => CitiesDropdown,
|
|
24
|
+
useAutocomplete: () => useAutocomplete,
|
|
25
|
+
useCities: () => useCities,
|
|
26
|
+
useCountries: () => useCountries,
|
|
27
|
+
usePlacesLayerClient: () => usePlacesLayerClient,
|
|
28
|
+
useProvinces: () => useProvinces
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(src_exports);
|
|
31
|
+
|
|
32
|
+
// src/CitiesDropdown.tsx
|
|
33
|
+
var import_react2 = require("react");
|
|
34
|
+
var import_react_native = require("react-native");
|
|
35
|
+
var import_sdk = require("@placeslayer/sdk");
|
|
36
|
+
|
|
37
|
+
// src/hooks/useAutocomplete.ts
|
|
38
|
+
var import_react = require("react");
|
|
39
|
+
var DEBOUNCE_MS = 300;
|
|
40
|
+
function useAutocomplete(client, countryCode, query, opts) {
|
|
41
|
+
const [results, setResults] = (0, import_react.useState)([]);
|
|
42
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
43
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
44
|
+
const version = (0, import_react.useRef)(0);
|
|
45
|
+
const timerRef = (0, import_react.useRef)(void 0);
|
|
46
|
+
const delay = opts?.debounceMs ?? DEBOUNCE_MS;
|
|
47
|
+
const search = (0, import_react.useCallback)(() => {
|
|
48
|
+
const trimmed = query.trim();
|
|
49
|
+
if (!trimmed) {
|
|
50
|
+
setResults([]);
|
|
51
|
+
setLoading(false);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const current = ++version.current;
|
|
55
|
+
setLoading(true);
|
|
56
|
+
client.autocomplete(countryCode, trimmed, { limit: opts?.limit }).then((data) => {
|
|
57
|
+
if (current === version.current) {
|
|
58
|
+
setResults(data);
|
|
59
|
+
setLoading(false);
|
|
60
|
+
}
|
|
61
|
+
}).catch((err) => {
|
|
62
|
+
if (current === version.current) {
|
|
63
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
64
|
+
setLoading(false);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}, [client, countryCode, query, opts?.limit]);
|
|
68
|
+
(0, import_react.useEffect)(() => {
|
|
69
|
+
if (timerRef.current !== void 0) clearTimeout(timerRef.current);
|
|
70
|
+
timerRef.current = setTimeout(search, delay);
|
|
71
|
+
return () => {
|
|
72
|
+
if (timerRef.current !== void 0) clearTimeout(timerRef.current);
|
|
73
|
+
};
|
|
74
|
+
}, [search, delay]);
|
|
75
|
+
return { results, loading, error };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/CitiesDropdown.tsx
|
|
79
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
80
|
+
function CitiesDropdown({
|
|
81
|
+
apiKey,
|
|
82
|
+
country,
|
|
83
|
+
province,
|
|
84
|
+
onSelect,
|
|
85
|
+
limit = 10,
|
|
86
|
+
placeholder = "Search cities...",
|
|
87
|
+
baseUrl,
|
|
88
|
+
theme,
|
|
89
|
+
style,
|
|
90
|
+
inputStyle
|
|
91
|
+
}) {
|
|
92
|
+
const clientRef = (0, import_react2.useRef)(null);
|
|
93
|
+
if (!clientRef.current || clientRef.current.apiKey !== apiKey) {
|
|
94
|
+
clientRef.current = new import_sdk.PlacesLayerClient({ apiKey, baseUrl });
|
|
95
|
+
}
|
|
96
|
+
const client = clientRef.current;
|
|
97
|
+
const [query, setQuery] = (0, import_react2.useState)("");
|
|
98
|
+
const [open, setOpen] = (0, import_react2.useState)(false);
|
|
99
|
+
const prevCountry = (0, import_react2.useRef)(country);
|
|
100
|
+
const prevProvince = (0, import_react2.useRef)(province);
|
|
101
|
+
(0, import_react2.useEffect)(() => {
|
|
102
|
+
if (prevCountry.current !== country || prevProvince.current !== province) {
|
|
103
|
+
setQuery("");
|
|
104
|
+
setOpen(false);
|
|
105
|
+
prevCountry.current = country;
|
|
106
|
+
prevProvince.current = province;
|
|
107
|
+
}
|
|
108
|
+
}, [country, province]);
|
|
109
|
+
const { results, loading } = useAutocomplete(client, country, query, {
|
|
110
|
+
limit
|
|
111
|
+
});
|
|
112
|
+
const filtered = province ? results.filter(
|
|
113
|
+
(r) => r.type === "city" && r.province_code === province
|
|
114
|
+
) : results;
|
|
115
|
+
const handleSelect = (0, import_react2.useCallback)(
|
|
116
|
+
async (result) => {
|
|
117
|
+
setQuery(result.name);
|
|
118
|
+
setOpen(false);
|
|
119
|
+
const detail = await client.city(result.country_code, result.code);
|
|
120
|
+
onSelect(detail);
|
|
121
|
+
},
|
|
122
|
+
[client, onSelect]
|
|
123
|
+
);
|
|
124
|
+
const t = theme ?? {};
|
|
125
|
+
const borderColor = t.borderColor ?? "#d1d5db";
|
|
126
|
+
const borderRadius = t.borderRadius ?? 6;
|
|
127
|
+
const background = t.background ?? "#fff";
|
|
128
|
+
const textColor = t.textColor ?? "#111827";
|
|
129
|
+
const secondaryTextColor = t.secondaryTextColor ?? "#6b7280";
|
|
130
|
+
const activeBackground = t.activeBackground ?? "#f3f4f6";
|
|
131
|
+
const fontSize = t.fontSize ?? 14;
|
|
132
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: [styles.container, style], children: [
|
|
133
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
134
|
+
import_react_native.TextInput,
|
|
135
|
+
{
|
|
136
|
+
value: query,
|
|
137
|
+
onChangeText: (text) => {
|
|
138
|
+
setQuery(text);
|
|
139
|
+
setOpen(true);
|
|
140
|
+
},
|
|
141
|
+
onFocus: () => {
|
|
142
|
+
if (query.trim()) setOpen(true);
|
|
143
|
+
},
|
|
144
|
+
placeholder,
|
|
145
|
+
placeholderTextColor: secondaryTextColor,
|
|
146
|
+
style: [
|
|
147
|
+
styles.input,
|
|
148
|
+
{
|
|
149
|
+
borderColor,
|
|
150
|
+
borderRadius,
|
|
151
|
+
backgroundColor: background,
|
|
152
|
+
color: textColor,
|
|
153
|
+
fontSize
|
|
154
|
+
},
|
|
155
|
+
inputStyle
|
|
156
|
+
],
|
|
157
|
+
accessibilityRole: "search"
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
open && filtered.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
161
|
+
import_react_native.View,
|
|
162
|
+
{
|
|
163
|
+
style: [
|
|
164
|
+
styles.list,
|
|
165
|
+
{ borderColor, borderRadius, backgroundColor: background }
|
|
166
|
+
],
|
|
167
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
168
|
+
import_react_native.FlatList,
|
|
169
|
+
{
|
|
170
|
+
data: filtered,
|
|
171
|
+
keyExtractor: (item) => item.code,
|
|
172
|
+
keyboardShouldPersistTaps: "handled",
|
|
173
|
+
renderItem: ({ item }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
174
|
+
import_react_native.Pressable,
|
|
175
|
+
{
|
|
176
|
+
style: ({ pressed }) => [
|
|
177
|
+
styles.item,
|
|
178
|
+
pressed && { backgroundColor: activeBackground }
|
|
179
|
+
],
|
|
180
|
+
onPress: () => handleSelect(item),
|
|
181
|
+
accessibilityRole: "button",
|
|
182
|
+
children: [
|
|
183
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
184
|
+
import_react_native.Text,
|
|
185
|
+
{
|
|
186
|
+
style: [styles.name, { color: textColor, fontSize }],
|
|
187
|
+
numberOfLines: 1,
|
|
188
|
+
children: item.name
|
|
189
|
+
}
|
|
190
|
+
),
|
|
191
|
+
item.province_name && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
192
|
+
import_react_native.Text,
|
|
193
|
+
{
|
|
194
|
+
style: [styles.province, { color: secondaryTextColor }],
|
|
195
|
+
numberOfLines: 1,
|
|
196
|
+
children: item.province_name
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
),
|
|
206
|
+
open && loading && filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
207
|
+
import_react_native.View,
|
|
208
|
+
{
|
|
209
|
+
style: [
|
|
210
|
+
styles.status,
|
|
211
|
+
{ borderColor, borderRadius, backgroundColor: background }
|
|
212
|
+
],
|
|
213
|
+
children: [
|
|
214
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.ActivityIndicator, { size: "small", color: secondaryTextColor }),
|
|
215
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
216
|
+
import_react_native.Text,
|
|
217
|
+
{
|
|
218
|
+
style: [
|
|
219
|
+
styles.statusText,
|
|
220
|
+
{ color: secondaryTextColor, fontSize }
|
|
221
|
+
],
|
|
222
|
+
children: "Loading..."
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
] });
|
|
229
|
+
}
|
|
230
|
+
var styles = import_react_native.StyleSheet.create({
|
|
231
|
+
container: {
|
|
232
|
+
position: "relative",
|
|
233
|
+
width: "100%"
|
|
234
|
+
},
|
|
235
|
+
input: {
|
|
236
|
+
width: "100%",
|
|
237
|
+
paddingVertical: 10,
|
|
238
|
+
paddingHorizontal: 12,
|
|
239
|
+
borderWidth: 1
|
|
240
|
+
},
|
|
241
|
+
list: {
|
|
242
|
+
position: "absolute",
|
|
243
|
+
top: "100%",
|
|
244
|
+
left: 0,
|
|
245
|
+
right: 0,
|
|
246
|
+
marginTop: 4,
|
|
247
|
+
borderWidth: 1,
|
|
248
|
+
maxHeight: 240,
|
|
249
|
+
zIndex: 50,
|
|
250
|
+
shadowColor: "#000",
|
|
251
|
+
shadowOffset: { width: 0, height: 2 },
|
|
252
|
+
shadowOpacity: 0.1,
|
|
253
|
+
shadowRadius: 4,
|
|
254
|
+
elevation: 4,
|
|
255
|
+
overflow: "hidden"
|
|
256
|
+
},
|
|
257
|
+
item: {
|
|
258
|
+
paddingVertical: 10,
|
|
259
|
+
paddingHorizontal: 12,
|
|
260
|
+
flexDirection: "row",
|
|
261
|
+
justifyContent: "space-between",
|
|
262
|
+
alignItems: "center"
|
|
263
|
+
},
|
|
264
|
+
name: {
|
|
265
|
+
fontWeight: "500",
|
|
266
|
+
flexShrink: 1
|
|
267
|
+
},
|
|
268
|
+
province: {
|
|
269
|
+
fontSize: 12,
|
|
270
|
+
marginLeft: 8
|
|
271
|
+
},
|
|
272
|
+
status: {
|
|
273
|
+
position: "absolute",
|
|
274
|
+
top: "100%",
|
|
275
|
+
left: 0,
|
|
276
|
+
right: 0,
|
|
277
|
+
marginTop: 4,
|
|
278
|
+
borderWidth: 1,
|
|
279
|
+
padding: 10,
|
|
280
|
+
flexDirection: "row",
|
|
281
|
+
alignItems: "center"
|
|
282
|
+
},
|
|
283
|
+
statusText: {
|
|
284
|
+
marginLeft: 8
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// src/hooks/usePlacesLayer.ts
|
|
289
|
+
var import_react3 = require("react");
|
|
290
|
+
var import_sdk2 = require("@placeslayer/sdk");
|
|
291
|
+
function usePlacesLayerClient(config) {
|
|
292
|
+
const ref = (0, import_react3.useRef)(null);
|
|
293
|
+
if (!ref.current || ref.current.apiKey !== config.apiKey) {
|
|
294
|
+
ref.current = new import_sdk2.PlacesLayerClient(config);
|
|
295
|
+
}
|
|
296
|
+
return ref.current;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/hooks/useAsync.ts
|
|
300
|
+
var import_react4 = require("react");
|
|
301
|
+
function useAsync(fn, deps) {
|
|
302
|
+
const [data, setData] = (0, import_react4.useState)(null);
|
|
303
|
+
const [loading, setLoading] = (0, import_react4.useState)(true);
|
|
304
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
305
|
+
const version = (0, import_react4.useRef)(0);
|
|
306
|
+
const execute = (0, import_react4.useCallback)(() => {
|
|
307
|
+
const current = ++version.current;
|
|
308
|
+
setLoading(true);
|
|
309
|
+
setError(null);
|
|
310
|
+
fn().then((result) => {
|
|
311
|
+
if (current === version.current) {
|
|
312
|
+
setData(result);
|
|
313
|
+
setLoading(false);
|
|
314
|
+
}
|
|
315
|
+
}).catch((err) => {
|
|
316
|
+
if (current === version.current) {
|
|
317
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
318
|
+
setLoading(false);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}, deps);
|
|
322
|
+
(0, import_react4.useEffect)(() => {
|
|
323
|
+
execute();
|
|
324
|
+
}, [execute]);
|
|
325
|
+
return { data, loading, error, refetch: execute };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/hooks/useCountries.ts
|
|
329
|
+
function useCountries(client) {
|
|
330
|
+
return useAsync(() => client.countries(), [client]);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/hooks/useProvinces.ts
|
|
334
|
+
function useProvinces(client, countryCode, opts) {
|
|
335
|
+
return useAsync(
|
|
336
|
+
() => client.provinces(countryCode, opts),
|
|
337
|
+
[client, countryCode, opts?.native]
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/hooks/useCities.ts
|
|
342
|
+
function useCities(client, countryCode, provinceCode, opts) {
|
|
343
|
+
return useAsync(
|
|
344
|
+
() => client.cities(countryCode, provinceCode, opts),
|
|
345
|
+
[client, countryCode, provinceCode, opts?.native]
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
349
|
+
0 && (module.exports = {
|
|
350
|
+
CitiesDropdown,
|
|
351
|
+
useAutocomplete,
|
|
352
|
+
useCities,
|
|
353
|
+
useCountries,
|
|
354
|
+
usePlacesLayerClient,
|
|
355
|
+
useProvinces
|
|
356
|
+
});
|
|
357
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/CitiesDropdown.tsx","../src/hooks/useAutocomplete.ts","../src/hooks/usePlacesLayer.ts","../src/hooks/useAsync.ts","../src/hooks/useCountries.ts","../src/hooks/useProvinces.ts","../src/hooks/useCities.ts"],"sourcesContent":["export { CitiesDropdown, type CitiesDropdownProps, type CitiesDropdownTheme } from \"./CitiesDropdown\";\nexport { usePlacesLayerClient } from \"./hooks/usePlacesLayer\";\nexport { useCountries } from \"./hooks/useCountries\";\nexport { useProvinces } from \"./hooks/useProvinces\";\nexport { useCities } from \"./hooks/useCities\";\nexport { useAutocomplete, type AutocompleteState } from \"./hooks/useAutocomplete\";\nexport { type AsyncState } from \"./hooks/useAsync\";\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n ActivityIndicator,\n FlatList,\n Pressable,\n StyleSheet,\n Text,\n TextInput,\n View,\n type StyleProp,\n type TextStyle,\n type ViewStyle,\n} from \"react-native\";\nimport {\n PlacesLayerClient,\n type AutocompleteResult,\n type CityDetail,\n} from \"@placeslayer/sdk\";\nimport { useAutocomplete } from \"./hooks/useAutocomplete\";\n\nexport interface CitiesDropdownTheme {\n /** Input/list border color (default: \"#d1d5db\") */\n borderColor?: string;\n /** Input/list border radius (default: 6) */\n borderRadius?: number;\n /** Dropdown background color (default: \"#fff\") */\n background?: string;\n /** Main text color (default: \"#111827\") */\n textColor?: string;\n /** Secondary text color for province labels (default: \"#6b7280\") */\n secondaryTextColor?: string;\n /** Active/pressed item background (default: \"#f3f4f6\") */\n activeBackground?: string;\n /** Input font size (default: 14) */\n fontSize?: number;\n}\n\nexport interface CitiesDropdownProps {\n apiKey: string;\n country: string;\n province?: string;\n onSelect: (city: CityDetail) => void;\n limit?: number;\n lang?: \"default\" | \"native\";\n placeholder?: string;\n baseUrl?: string;\n /** Customize dropdown colors and appearance */\n theme?: CitiesDropdownTheme;\n /** Custom container style */\n style?: StyleProp<ViewStyle>;\n /** Custom input style (merged on top of theme) */\n inputStyle?: StyleProp<TextStyle>;\n}\n\nexport function CitiesDropdown({\n apiKey,\n country,\n province,\n onSelect,\n limit = 10,\n placeholder = \"Search cities...\",\n baseUrl,\n theme,\n style,\n inputStyle,\n}: CitiesDropdownProps) {\n const clientRef = useRef<PlacesLayerClient | null>(null);\n if (\n !clientRef.current ||\n (clientRef.current as unknown as { apiKey: string }).apiKey !== apiKey\n ) {\n clientRef.current = new PlacesLayerClient({ apiKey, baseUrl });\n }\n const client = clientRef.current;\n\n const [query, setQuery] = useState(\"\");\n const [open, setOpen] = useState(false);\n\n // Reset input when country or province changes\n const prevCountry = useRef(country);\n const prevProvince = useRef(province);\n useEffect(() => {\n if (prevCountry.current !== country || prevProvince.current !== province) {\n setQuery(\"\");\n setOpen(false);\n prevCountry.current = country;\n prevProvince.current = province;\n }\n }, [country, province]);\n\n const { results, loading } = useAutocomplete(client, country, query, {\n limit,\n });\n\n const filtered = province\n ? results.filter(\n (r) => r.type === \"city\" && r.province_code === province\n )\n : results;\n\n const handleSelect = useCallback(\n async (result: AutocompleteResult) => {\n setQuery(result.name);\n setOpen(false);\n const detail = await client.city(result.country_code, result.code);\n onSelect(detail);\n },\n [client, onSelect]\n );\n\n const t = theme ?? {};\n const borderColor = t.borderColor ?? \"#d1d5db\";\n const borderRadius = t.borderRadius ?? 6;\n const background = t.background ?? \"#fff\";\n const textColor = t.textColor ?? \"#111827\";\n const secondaryTextColor = t.secondaryTextColor ?? \"#6b7280\";\n const activeBackground = t.activeBackground ?? \"#f3f4f6\";\n const fontSize = t.fontSize ?? 14;\n\n return (\n <View style={[styles.container, style]}>\n <TextInput\n value={query}\n onChangeText={(text) => {\n setQuery(text);\n setOpen(true);\n }}\n onFocus={() => {\n if (query.trim()) setOpen(true);\n }}\n placeholder={placeholder}\n placeholderTextColor={secondaryTextColor}\n style={[\n styles.input,\n {\n borderColor,\n borderRadius,\n backgroundColor: background,\n color: textColor,\n fontSize,\n },\n inputStyle,\n ]}\n accessibilityRole=\"search\"\n />\n {open && filtered.length > 0 && (\n <View\n style={[\n styles.list,\n { borderColor, borderRadius, backgroundColor: background },\n ]}\n >\n <FlatList\n data={filtered}\n keyExtractor={(item) => item.code}\n keyboardShouldPersistTaps=\"handled\"\n renderItem={({ item }) => (\n <Pressable\n style={({ pressed }) => [\n styles.item,\n pressed && { backgroundColor: activeBackground },\n ]}\n onPress={() => handleSelect(item)}\n accessibilityRole=\"button\"\n >\n <Text\n style={[styles.name, { color: textColor, fontSize }]}\n numberOfLines={1}\n >\n {item.name}\n </Text>\n {item.province_name && (\n <Text\n style={[styles.province, { color: secondaryTextColor }]}\n numberOfLines={1}\n >\n {item.province_name}\n </Text>\n )}\n </Pressable>\n )}\n />\n </View>\n )}\n {open && loading && filtered.length === 0 && (\n <View\n style={[\n styles.status,\n { borderColor, borderRadius, backgroundColor: background },\n ]}\n >\n <ActivityIndicator size=\"small\" color={secondaryTextColor} />\n <Text\n style={[\n styles.statusText,\n { color: secondaryTextColor, fontSize },\n ]}\n >\n Loading...\n </Text>\n </View>\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n position: \"relative\",\n width: \"100%\",\n },\n input: {\n width: \"100%\",\n paddingVertical: 10,\n paddingHorizontal: 12,\n borderWidth: 1,\n },\n list: {\n position: \"absolute\",\n top: \"100%\",\n left: 0,\n right: 0,\n marginTop: 4,\n borderWidth: 1,\n maxHeight: 240,\n zIndex: 50,\n shadowColor: \"#000\",\n shadowOffset: { width: 0, height: 2 },\n shadowOpacity: 0.1,\n shadowRadius: 4,\n elevation: 4,\n overflow: \"hidden\",\n },\n item: {\n paddingVertical: 10,\n paddingHorizontal: 12,\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n },\n name: {\n fontWeight: \"500\",\n flexShrink: 1,\n },\n province: {\n fontSize: 12,\n marginLeft: 8,\n },\n status: {\n position: \"absolute\",\n top: \"100%\",\n left: 0,\n right: 0,\n marginTop: 4,\n borderWidth: 1,\n padding: 10,\n flexDirection: \"row\",\n alignItems: \"center\",\n },\n statusText: {\n marginLeft: 8,\n },\n});\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { AutocompleteResult, PlacesLayerClient } from \"@placeslayer/sdk\";\n\nconst DEBOUNCE_MS = 300;\n\nexport interface AutocompleteState {\n results: AutocompleteResult[];\n loading: boolean;\n error: Error | null;\n}\n\nexport function useAutocomplete(\n client: PlacesLayerClient,\n countryCode: string,\n query: string,\n opts?: { limit?: number; debounceMs?: number }\n): AutocompleteState {\n const [results, setResults] = useState<AutocompleteResult[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const version = useRef(0);\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n const delay = opts?.debounceMs ?? DEBOUNCE_MS;\n\n const search = useCallback(() => {\n const trimmed = query.trim();\n if (!trimmed) {\n setResults([]);\n setLoading(false);\n return;\n }\n\n const current = ++version.current;\n setLoading(true);\n\n client\n .autocomplete(countryCode, trimmed, { limit: opts?.limit })\n .then((data) => {\n if (current === version.current) {\n setResults(data);\n setLoading(false);\n }\n })\n .catch((err) => {\n if (current === version.current) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n });\n }, [client, countryCode, query, opts?.limit]);\n\n useEffect(() => {\n if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(search, delay);\n return () => {\n if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n };\n }, [search, delay]);\n\n return { results, loading, error };\n}\n","import { useRef } from \"react\";\nimport { PlacesLayerClient, type PlacesLayerConfig } from \"@placeslayer/sdk\";\n\nexport function usePlacesLayerClient(config: PlacesLayerConfig): PlacesLayerClient {\n const ref = useRef<PlacesLayerClient | null>(null);\n\n if (!ref.current || (ref.current as unknown as { apiKey: string }).apiKey !== config.apiKey) {\n ref.current = new PlacesLayerClient(config);\n }\n\n return ref.current;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface AsyncState<T> {\n data: T | null;\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useAsync<T>(\n fn: () => Promise<T>,\n deps: unknown[]\n): AsyncState<T> {\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const version = useRef(0);\n\n const execute = useCallback(() => {\n const current = ++version.current;\n setLoading(true);\n setError(null);\n\n fn()\n .then((result) => {\n if (current === version.current) {\n setData(result);\n setLoading(false);\n }\n })\n .catch((err) => {\n if (current === version.current) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n\n useEffect(() => {\n execute();\n }, [execute]);\n\n return { data, loading, error, refetch: execute };\n}\n","import type { Country, PlacesLayerClient } from \"@placeslayer/sdk\";\nimport { type AsyncState, useAsync } from \"./useAsync\";\n\nexport function useCountries(client: PlacesLayerClient): AsyncState<Country[]> {\n return useAsync(() => client.countries(), [client]);\n}\n","import type { PlacesLayerClient, Province } from \"@placeslayer/sdk\";\nimport { type AsyncState, useAsync } from \"./useAsync\";\n\nexport function useProvinces(\n client: PlacesLayerClient,\n countryCode: string,\n opts?: { native?: boolean }\n): AsyncState<Province[]> {\n return useAsync(\n () => client.provinces(countryCode, opts),\n [client, countryCode, opts?.native]\n );\n}\n","import type { City, PlacesLayerClient } from \"@placeslayer/sdk\";\nimport { type AsyncState, useAsync } from \"./useAsync\";\n\nexport function useCities(\n client: PlacesLayerClient,\n countryCode: string,\n provinceCode: string,\n opts?: { native?: boolean }\n): AsyncState<City[]> {\n return useAsync(\n () => client.cities(countryCode, provinceCode, opts),\n [client, countryCode, provinceCode, opts?.native]\n );\n}\n"],"mappings":"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","names":["import_react","import_react","import_sdk","import_react"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
3
|
+
import { CityDetail, PlacesLayerConfig, PlacesLayerClient, Country, Province, City, AutocompleteResult } from '@placeslayer/sdk';
|
|
4
|
+
|
|
5
|
+
interface CitiesDropdownTheme {
|
|
6
|
+
/** Input/list border color (default: "#d1d5db") */
|
|
7
|
+
borderColor?: string;
|
|
8
|
+
/** Input/list border radius (default: 6) */
|
|
9
|
+
borderRadius?: number;
|
|
10
|
+
/** Dropdown background color (default: "#fff") */
|
|
11
|
+
background?: string;
|
|
12
|
+
/** Main text color (default: "#111827") */
|
|
13
|
+
textColor?: string;
|
|
14
|
+
/** Secondary text color for province labels (default: "#6b7280") */
|
|
15
|
+
secondaryTextColor?: string;
|
|
16
|
+
/** Active/pressed item background (default: "#f3f4f6") */
|
|
17
|
+
activeBackground?: string;
|
|
18
|
+
/** Input font size (default: 14) */
|
|
19
|
+
fontSize?: number;
|
|
20
|
+
}
|
|
21
|
+
interface CitiesDropdownProps {
|
|
22
|
+
apiKey: string;
|
|
23
|
+
country: string;
|
|
24
|
+
province?: string;
|
|
25
|
+
onSelect: (city: CityDetail) => void;
|
|
26
|
+
limit?: number;
|
|
27
|
+
lang?: "default" | "native";
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
baseUrl?: string;
|
|
30
|
+
/** Customize dropdown colors and appearance */
|
|
31
|
+
theme?: CitiesDropdownTheme;
|
|
32
|
+
/** Custom container style */
|
|
33
|
+
style?: StyleProp<ViewStyle>;
|
|
34
|
+
/** Custom input style (merged on top of theme) */
|
|
35
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
36
|
+
}
|
|
37
|
+
declare function CitiesDropdown({ apiKey, country, province, onSelect, limit, placeholder, baseUrl, theme, style, inputStyle, }: CitiesDropdownProps): react_jsx_runtime.JSX.Element;
|
|
38
|
+
|
|
39
|
+
declare function usePlacesLayerClient(config: PlacesLayerConfig): PlacesLayerClient;
|
|
40
|
+
|
|
41
|
+
interface AsyncState<T> {
|
|
42
|
+
data: T | null;
|
|
43
|
+
loading: boolean;
|
|
44
|
+
error: Error | null;
|
|
45
|
+
refetch: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
declare function useCountries(client: PlacesLayerClient): AsyncState<Country[]>;
|
|
49
|
+
|
|
50
|
+
declare function useProvinces(client: PlacesLayerClient, countryCode: string, opts?: {
|
|
51
|
+
native?: boolean;
|
|
52
|
+
}): AsyncState<Province[]>;
|
|
53
|
+
|
|
54
|
+
declare function useCities(client: PlacesLayerClient, countryCode: string, provinceCode: string, opts?: {
|
|
55
|
+
native?: boolean;
|
|
56
|
+
}): AsyncState<City[]>;
|
|
57
|
+
|
|
58
|
+
interface AutocompleteState {
|
|
59
|
+
results: AutocompleteResult[];
|
|
60
|
+
loading: boolean;
|
|
61
|
+
error: Error | null;
|
|
62
|
+
}
|
|
63
|
+
declare function useAutocomplete(client: PlacesLayerClient, countryCode: string, query: string, opts?: {
|
|
64
|
+
limit?: number;
|
|
65
|
+
debounceMs?: number;
|
|
66
|
+
}): AutocompleteState;
|
|
67
|
+
|
|
68
|
+
export { type AsyncState, type AutocompleteState, CitiesDropdown, type CitiesDropdownProps, type CitiesDropdownTheme, useAutocomplete, useCities, useCountries, usePlacesLayerClient, useProvinces };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
3
|
+
import { CityDetail, PlacesLayerConfig, PlacesLayerClient, Country, Province, City, AutocompleteResult } from '@placeslayer/sdk';
|
|
4
|
+
|
|
5
|
+
interface CitiesDropdownTheme {
|
|
6
|
+
/** Input/list border color (default: "#d1d5db") */
|
|
7
|
+
borderColor?: string;
|
|
8
|
+
/** Input/list border radius (default: 6) */
|
|
9
|
+
borderRadius?: number;
|
|
10
|
+
/** Dropdown background color (default: "#fff") */
|
|
11
|
+
background?: string;
|
|
12
|
+
/** Main text color (default: "#111827") */
|
|
13
|
+
textColor?: string;
|
|
14
|
+
/** Secondary text color for province labels (default: "#6b7280") */
|
|
15
|
+
secondaryTextColor?: string;
|
|
16
|
+
/** Active/pressed item background (default: "#f3f4f6") */
|
|
17
|
+
activeBackground?: string;
|
|
18
|
+
/** Input font size (default: 14) */
|
|
19
|
+
fontSize?: number;
|
|
20
|
+
}
|
|
21
|
+
interface CitiesDropdownProps {
|
|
22
|
+
apiKey: string;
|
|
23
|
+
country: string;
|
|
24
|
+
province?: string;
|
|
25
|
+
onSelect: (city: CityDetail) => void;
|
|
26
|
+
limit?: number;
|
|
27
|
+
lang?: "default" | "native";
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
baseUrl?: string;
|
|
30
|
+
/** Customize dropdown colors and appearance */
|
|
31
|
+
theme?: CitiesDropdownTheme;
|
|
32
|
+
/** Custom container style */
|
|
33
|
+
style?: StyleProp<ViewStyle>;
|
|
34
|
+
/** Custom input style (merged on top of theme) */
|
|
35
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
36
|
+
}
|
|
37
|
+
declare function CitiesDropdown({ apiKey, country, province, onSelect, limit, placeholder, baseUrl, theme, style, inputStyle, }: CitiesDropdownProps): react_jsx_runtime.JSX.Element;
|
|
38
|
+
|
|
39
|
+
declare function usePlacesLayerClient(config: PlacesLayerConfig): PlacesLayerClient;
|
|
40
|
+
|
|
41
|
+
interface AsyncState<T> {
|
|
42
|
+
data: T | null;
|
|
43
|
+
loading: boolean;
|
|
44
|
+
error: Error | null;
|
|
45
|
+
refetch: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
declare function useCountries(client: PlacesLayerClient): AsyncState<Country[]>;
|
|
49
|
+
|
|
50
|
+
declare function useProvinces(client: PlacesLayerClient, countryCode: string, opts?: {
|
|
51
|
+
native?: boolean;
|
|
52
|
+
}): AsyncState<Province[]>;
|
|
53
|
+
|
|
54
|
+
declare function useCities(client: PlacesLayerClient, countryCode: string, provinceCode: string, opts?: {
|
|
55
|
+
native?: boolean;
|
|
56
|
+
}): AsyncState<City[]>;
|
|
57
|
+
|
|
58
|
+
interface AutocompleteState {
|
|
59
|
+
results: AutocompleteResult[];
|
|
60
|
+
loading: boolean;
|
|
61
|
+
error: Error | null;
|
|
62
|
+
}
|
|
63
|
+
declare function useAutocomplete(client: PlacesLayerClient, countryCode: string, query: string, opts?: {
|
|
64
|
+
limit?: number;
|
|
65
|
+
debounceMs?: number;
|
|
66
|
+
}): AutocompleteState;
|
|
67
|
+
|
|
68
|
+
export { type AsyncState, type AutocompleteState, CitiesDropdown, type CitiesDropdownProps, type CitiesDropdownTheme, useAutocomplete, useCities, useCountries, usePlacesLayerClient, useProvinces };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
// src/CitiesDropdown.tsx
|
|
2
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
3
|
+
import {
|
|
4
|
+
ActivityIndicator,
|
|
5
|
+
FlatList,
|
|
6
|
+
Pressable,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
Text,
|
|
9
|
+
TextInput,
|
|
10
|
+
View
|
|
11
|
+
} from "react-native";
|
|
12
|
+
import {
|
|
13
|
+
PlacesLayerClient
|
|
14
|
+
} from "@placeslayer/sdk";
|
|
15
|
+
|
|
16
|
+
// src/hooks/useAutocomplete.ts
|
|
17
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
18
|
+
var DEBOUNCE_MS = 300;
|
|
19
|
+
function useAutocomplete(client, countryCode, query, opts) {
|
|
20
|
+
const [results, setResults] = useState([]);
|
|
21
|
+
const [loading, setLoading] = useState(false);
|
|
22
|
+
const [error, setError] = useState(null);
|
|
23
|
+
const version = useRef(0);
|
|
24
|
+
const timerRef = useRef(void 0);
|
|
25
|
+
const delay = opts?.debounceMs ?? DEBOUNCE_MS;
|
|
26
|
+
const search = useCallback(() => {
|
|
27
|
+
const trimmed = query.trim();
|
|
28
|
+
if (!trimmed) {
|
|
29
|
+
setResults([]);
|
|
30
|
+
setLoading(false);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const current = ++version.current;
|
|
34
|
+
setLoading(true);
|
|
35
|
+
client.autocomplete(countryCode, trimmed, { limit: opts?.limit }).then((data) => {
|
|
36
|
+
if (current === version.current) {
|
|
37
|
+
setResults(data);
|
|
38
|
+
setLoading(false);
|
|
39
|
+
}
|
|
40
|
+
}).catch((err) => {
|
|
41
|
+
if (current === version.current) {
|
|
42
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
43
|
+
setLoading(false);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}, [client, countryCode, query, opts?.limit]);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (timerRef.current !== void 0) clearTimeout(timerRef.current);
|
|
49
|
+
timerRef.current = setTimeout(search, delay);
|
|
50
|
+
return () => {
|
|
51
|
+
if (timerRef.current !== void 0) clearTimeout(timerRef.current);
|
|
52
|
+
};
|
|
53
|
+
}, [search, delay]);
|
|
54
|
+
return { results, loading, error };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/CitiesDropdown.tsx
|
|
58
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
59
|
+
function CitiesDropdown({
|
|
60
|
+
apiKey,
|
|
61
|
+
country,
|
|
62
|
+
province,
|
|
63
|
+
onSelect,
|
|
64
|
+
limit = 10,
|
|
65
|
+
placeholder = "Search cities...",
|
|
66
|
+
baseUrl,
|
|
67
|
+
theme,
|
|
68
|
+
style,
|
|
69
|
+
inputStyle
|
|
70
|
+
}) {
|
|
71
|
+
const clientRef = useRef2(null);
|
|
72
|
+
if (!clientRef.current || clientRef.current.apiKey !== apiKey) {
|
|
73
|
+
clientRef.current = new PlacesLayerClient({ apiKey, baseUrl });
|
|
74
|
+
}
|
|
75
|
+
const client = clientRef.current;
|
|
76
|
+
const [query, setQuery] = useState2("");
|
|
77
|
+
const [open, setOpen] = useState2(false);
|
|
78
|
+
const prevCountry = useRef2(country);
|
|
79
|
+
const prevProvince = useRef2(province);
|
|
80
|
+
useEffect2(() => {
|
|
81
|
+
if (prevCountry.current !== country || prevProvince.current !== province) {
|
|
82
|
+
setQuery("");
|
|
83
|
+
setOpen(false);
|
|
84
|
+
prevCountry.current = country;
|
|
85
|
+
prevProvince.current = province;
|
|
86
|
+
}
|
|
87
|
+
}, [country, province]);
|
|
88
|
+
const { results, loading } = useAutocomplete(client, country, query, {
|
|
89
|
+
limit
|
|
90
|
+
});
|
|
91
|
+
const filtered = province ? results.filter(
|
|
92
|
+
(r) => r.type === "city" && r.province_code === province
|
|
93
|
+
) : results;
|
|
94
|
+
const handleSelect = useCallback2(
|
|
95
|
+
async (result) => {
|
|
96
|
+
setQuery(result.name);
|
|
97
|
+
setOpen(false);
|
|
98
|
+
const detail = await client.city(result.country_code, result.code);
|
|
99
|
+
onSelect(detail);
|
|
100
|
+
},
|
|
101
|
+
[client, onSelect]
|
|
102
|
+
);
|
|
103
|
+
const t = theme ?? {};
|
|
104
|
+
const borderColor = t.borderColor ?? "#d1d5db";
|
|
105
|
+
const borderRadius = t.borderRadius ?? 6;
|
|
106
|
+
const background = t.background ?? "#fff";
|
|
107
|
+
const textColor = t.textColor ?? "#111827";
|
|
108
|
+
const secondaryTextColor = t.secondaryTextColor ?? "#6b7280";
|
|
109
|
+
const activeBackground = t.activeBackground ?? "#f3f4f6";
|
|
110
|
+
const fontSize = t.fontSize ?? 14;
|
|
111
|
+
return /* @__PURE__ */ jsxs(View, { style: [styles.container, style], children: [
|
|
112
|
+
/* @__PURE__ */ jsx(
|
|
113
|
+
TextInput,
|
|
114
|
+
{
|
|
115
|
+
value: query,
|
|
116
|
+
onChangeText: (text) => {
|
|
117
|
+
setQuery(text);
|
|
118
|
+
setOpen(true);
|
|
119
|
+
},
|
|
120
|
+
onFocus: () => {
|
|
121
|
+
if (query.trim()) setOpen(true);
|
|
122
|
+
},
|
|
123
|
+
placeholder,
|
|
124
|
+
placeholderTextColor: secondaryTextColor,
|
|
125
|
+
style: [
|
|
126
|
+
styles.input,
|
|
127
|
+
{
|
|
128
|
+
borderColor,
|
|
129
|
+
borderRadius,
|
|
130
|
+
backgroundColor: background,
|
|
131
|
+
color: textColor,
|
|
132
|
+
fontSize
|
|
133
|
+
},
|
|
134
|
+
inputStyle
|
|
135
|
+
],
|
|
136
|
+
accessibilityRole: "search"
|
|
137
|
+
}
|
|
138
|
+
),
|
|
139
|
+
open && filtered.length > 0 && /* @__PURE__ */ jsx(
|
|
140
|
+
View,
|
|
141
|
+
{
|
|
142
|
+
style: [
|
|
143
|
+
styles.list,
|
|
144
|
+
{ borderColor, borderRadius, backgroundColor: background }
|
|
145
|
+
],
|
|
146
|
+
children: /* @__PURE__ */ jsx(
|
|
147
|
+
FlatList,
|
|
148
|
+
{
|
|
149
|
+
data: filtered,
|
|
150
|
+
keyExtractor: (item) => item.code,
|
|
151
|
+
keyboardShouldPersistTaps: "handled",
|
|
152
|
+
renderItem: ({ item }) => /* @__PURE__ */ jsxs(
|
|
153
|
+
Pressable,
|
|
154
|
+
{
|
|
155
|
+
style: ({ pressed }) => [
|
|
156
|
+
styles.item,
|
|
157
|
+
pressed && { backgroundColor: activeBackground }
|
|
158
|
+
],
|
|
159
|
+
onPress: () => handleSelect(item),
|
|
160
|
+
accessibilityRole: "button",
|
|
161
|
+
children: [
|
|
162
|
+
/* @__PURE__ */ jsx(
|
|
163
|
+
Text,
|
|
164
|
+
{
|
|
165
|
+
style: [styles.name, { color: textColor, fontSize }],
|
|
166
|
+
numberOfLines: 1,
|
|
167
|
+
children: item.name
|
|
168
|
+
}
|
|
169
|
+
),
|
|
170
|
+
item.province_name && /* @__PURE__ */ jsx(
|
|
171
|
+
Text,
|
|
172
|
+
{
|
|
173
|
+
style: [styles.province, { color: secondaryTextColor }],
|
|
174
|
+
numberOfLines: 1,
|
|
175
|
+
children: item.province_name
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
),
|
|
185
|
+
open && loading && filtered.length === 0 && /* @__PURE__ */ jsxs(
|
|
186
|
+
View,
|
|
187
|
+
{
|
|
188
|
+
style: [
|
|
189
|
+
styles.status,
|
|
190
|
+
{ borderColor, borderRadius, backgroundColor: background }
|
|
191
|
+
],
|
|
192
|
+
children: [
|
|
193
|
+
/* @__PURE__ */ jsx(ActivityIndicator, { size: "small", color: secondaryTextColor }),
|
|
194
|
+
/* @__PURE__ */ jsx(
|
|
195
|
+
Text,
|
|
196
|
+
{
|
|
197
|
+
style: [
|
|
198
|
+
styles.statusText,
|
|
199
|
+
{ color: secondaryTextColor, fontSize }
|
|
200
|
+
],
|
|
201
|
+
children: "Loading..."
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
] });
|
|
208
|
+
}
|
|
209
|
+
var styles = StyleSheet.create({
|
|
210
|
+
container: {
|
|
211
|
+
position: "relative",
|
|
212
|
+
width: "100%"
|
|
213
|
+
},
|
|
214
|
+
input: {
|
|
215
|
+
width: "100%",
|
|
216
|
+
paddingVertical: 10,
|
|
217
|
+
paddingHorizontal: 12,
|
|
218
|
+
borderWidth: 1
|
|
219
|
+
},
|
|
220
|
+
list: {
|
|
221
|
+
position: "absolute",
|
|
222
|
+
top: "100%",
|
|
223
|
+
left: 0,
|
|
224
|
+
right: 0,
|
|
225
|
+
marginTop: 4,
|
|
226
|
+
borderWidth: 1,
|
|
227
|
+
maxHeight: 240,
|
|
228
|
+
zIndex: 50,
|
|
229
|
+
shadowColor: "#000",
|
|
230
|
+
shadowOffset: { width: 0, height: 2 },
|
|
231
|
+
shadowOpacity: 0.1,
|
|
232
|
+
shadowRadius: 4,
|
|
233
|
+
elevation: 4,
|
|
234
|
+
overflow: "hidden"
|
|
235
|
+
},
|
|
236
|
+
item: {
|
|
237
|
+
paddingVertical: 10,
|
|
238
|
+
paddingHorizontal: 12,
|
|
239
|
+
flexDirection: "row",
|
|
240
|
+
justifyContent: "space-between",
|
|
241
|
+
alignItems: "center"
|
|
242
|
+
},
|
|
243
|
+
name: {
|
|
244
|
+
fontWeight: "500",
|
|
245
|
+
flexShrink: 1
|
|
246
|
+
},
|
|
247
|
+
province: {
|
|
248
|
+
fontSize: 12,
|
|
249
|
+
marginLeft: 8
|
|
250
|
+
},
|
|
251
|
+
status: {
|
|
252
|
+
position: "absolute",
|
|
253
|
+
top: "100%",
|
|
254
|
+
left: 0,
|
|
255
|
+
right: 0,
|
|
256
|
+
marginTop: 4,
|
|
257
|
+
borderWidth: 1,
|
|
258
|
+
padding: 10,
|
|
259
|
+
flexDirection: "row",
|
|
260
|
+
alignItems: "center"
|
|
261
|
+
},
|
|
262
|
+
statusText: {
|
|
263
|
+
marginLeft: 8
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// src/hooks/usePlacesLayer.ts
|
|
268
|
+
import { useRef as useRef3 } from "react";
|
|
269
|
+
import { PlacesLayerClient as PlacesLayerClient2 } from "@placeslayer/sdk";
|
|
270
|
+
function usePlacesLayerClient(config) {
|
|
271
|
+
const ref = useRef3(null);
|
|
272
|
+
if (!ref.current || ref.current.apiKey !== config.apiKey) {
|
|
273
|
+
ref.current = new PlacesLayerClient2(config);
|
|
274
|
+
}
|
|
275
|
+
return ref.current;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/hooks/useAsync.ts
|
|
279
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef4, useState as useState3 } from "react";
|
|
280
|
+
function useAsync(fn, deps) {
|
|
281
|
+
const [data, setData] = useState3(null);
|
|
282
|
+
const [loading, setLoading] = useState3(true);
|
|
283
|
+
const [error, setError] = useState3(null);
|
|
284
|
+
const version = useRef4(0);
|
|
285
|
+
const execute = useCallback3(() => {
|
|
286
|
+
const current = ++version.current;
|
|
287
|
+
setLoading(true);
|
|
288
|
+
setError(null);
|
|
289
|
+
fn().then((result) => {
|
|
290
|
+
if (current === version.current) {
|
|
291
|
+
setData(result);
|
|
292
|
+
setLoading(false);
|
|
293
|
+
}
|
|
294
|
+
}).catch((err) => {
|
|
295
|
+
if (current === version.current) {
|
|
296
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
297
|
+
setLoading(false);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}, deps);
|
|
301
|
+
useEffect3(() => {
|
|
302
|
+
execute();
|
|
303
|
+
}, [execute]);
|
|
304
|
+
return { data, loading, error, refetch: execute };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/hooks/useCountries.ts
|
|
308
|
+
function useCountries(client) {
|
|
309
|
+
return useAsync(() => client.countries(), [client]);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/hooks/useProvinces.ts
|
|
313
|
+
function useProvinces(client, countryCode, opts) {
|
|
314
|
+
return useAsync(
|
|
315
|
+
() => client.provinces(countryCode, opts),
|
|
316
|
+
[client, countryCode, opts?.native]
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/hooks/useCities.ts
|
|
321
|
+
function useCities(client, countryCode, provinceCode, opts) {
|
|
322
|
+
return useAsync(
|
|
323
|
+
() => client.cities(countryCode, provinceCode, opts),
|
|
324
|
+
[client, countryCode, provinceCode, opts?.native]
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
export {
|
|
328
|
+
CitiesDropdown,
|
|
329
|
+
useAutocomplete,
|
|
330
|
+
useCities,
|
|
331
|
+
useCountries,
|
|
332
|
+
usePlacesLayerClient,
|
|
333
|
+
useProvinces
|
|
334
|
+
};
|
|
335
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/CitiesDropdown.tsx","../src/hooks/useAutocomplete.ts","../src/hooks/usePlacesLayer.ts","../src/hooks/useAsync.ts","../src/hooks/useCountries.ts","../src/hooks/useProvinces.ts","../src/hooks/useCities.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n ActivityIndicator,\n FlatList,\n Pressable,\n StyleSheet,\n Text,\n TextInput,\n View,\n type StyleProp,\n type TextStyle,\n type ViewStyle,\n} from \"react-native\";\nimport {\n PlacesLayerClient,\n type AutocompleteResult,\n type CityDetail,\n} from \"@placeslayer/sdk\";\nimport { useAutocomplete } from \"./hooks/useAutocomplete\";\n\nexport interface CitiesDropdownTheme {\n /** Input/list border color (default: \"#d1d5db\") */\n borderColor?: string;\n /** Input/list border radius (default: 6) */\n borderRadius?: number;\n /** Dropdown background color (default: \"#fff\") */\n background?: string;\n /** Main text color (default: \"#111827\") */\n textColor?: string;\n /** Secondary text color for province labels (default: \"#6b7280\") */\n secondaryTextColor?: string;\n /** Active/pressed item background (default: \"#f3f4f6\") */\n activeBackground?: string;\n /** Input font size (default: 14) */\n fontSize?: number;\n}\n\nexport interface CitiesDropdownProps {\n apiKey: string;\n country: string;\n province?: string;\n onSelect: (city: CityDetail) => void;\n limit?: number;\n lang?: \"default\" | \"native\";\n placeholder?: string;\n baseUrl?: string;\n /** Customize dropdown colors and appearance */\n theme?: CitiesDropdownTheme;\n /** Custom container style */\n style?: StyleProp<ViewStyle>;\n /** Custom input style (merged on top of theme) */\n inputStyle?: StyleProp<TextStyle>;\n}\n\nexport function CitiesDropdown({\n apiKey,\n country,\n province,\n onSelect,\n limit = 10,\n placeholder = \"Search cities...\",\n baseUrl,\n theme,\n style,\n inputStyle,\n}: CitiesDropdownProps) {\n const clientRef = useRef<PlacesLayerClient | null>(null);\n if (\n !clientRef.current ||\n (clientRef.current as unknown as { apiKey: string }).apiKey !== apiKey\n ) {\n clientRef.current = new PlacesLayerClient({ apiKey, baseUrl });\n }\n const client = clientRef.current;\n\n const [query, setQuery] = useState(\"\");\n const [open, setOpen] = useState(false);\n\n // Reset input when country or province changes\n const prevCountry = useRef(country);\n const prevProvince = useRef(province);\n useEffect(() => {\n if (prevCountry.current !== country || prevProvince.current !== province) {\n setQuery(\"\");\n setOpen(false);\n prevCountry.current = country;\n prevProvince.current = province;\n }\n }, [country, province]);\n\n const { results, loading } = useAutocomplete(client, country, query, {\n limit,\n });\n\n const filtered = province\n ? results.filter(\n (r) => r.type === \"city\" && r.province_code === province\n )\n : results;\n\n const handleSelect = useCallback(\n async (result: AutocompleteResult) => {\n setQuery(result.name);\n setOpen(false);\n const detail = await client.city(result.country_code, result.code);\n onSelect(detail);\n },\n [client, onSelect]\n );\n\n const t = theme ?? {};\n const borderColor = t.borderColor ?? \"#d1d5db\";\n const borderRadius = t.borderRadius ?? 6;\n const background = t.background ?? \"#fff\";\n const textColor = t.textColor ?? \"#111827\";\n const secondaryTextColor = t.secondaryTextColor ?? \"#6b7280\";\n const activeBackground = t.activeBackground ?? \"#f3f4f6\";\n const fontSize = t.fontSize ?? 14;\n\n return (\n <View style={[styles.container, style]}>\n <TextInput\n value={query}\n onChangeText={(text) => {\n setQuery(text);\n setOpen(true);\n }}\n onFocus={() => {\n if (query.trim()) setOpen(true);\n }}\n placeholder={placeholder}\n placeholderTextColor={secondaryTextColor}\n style={[\n styles.input,\n {\n borderColor,\n borderRadius,\n backgroundColor: background,\n color: textColor,\n fontSize,\n },\n inputStyle,\n ]}\n accessibilityRole=\"search\"\n />\n {open && filtered.length > 0 && (\n <View\n style={[\n styles.list,\n { borderColor, borderRadius, backgroundColor: background },\n ]}\n >\n <FlatList\n data={filtered}\n keyExtractor={(item) => item.code}\n keyboardShouldPersistTaps=\"handled\"\n renderItem={({ item }) => (\n <Pressable\n style={({ pressed }) => [\n styles.item,\n pressed && { backgroundColor: activeBackground },\n ]}\n onPress={() => handleSelect(item)}\n accessibilityRole=\"button\"\n >\n <Text\n style={[styles.name, { color: textColor, fontSize }]}\n numberOfLines={1}\n >\n {item.name}\n </Text>\n {item.province_name && (\n <Text\n style={[styles.province, { color: secondaryTextColor }]}\n numberOfLines={1}\n >\n {item.province_name}\n </Text>\n )}\n </Pressable>\n )}\n />\n </View>\n )}\n {open && loading && filtered.length === 0 && (\n <View\n style={[\n styles.status,\n { borderColor, borderRadius, backgroundColor: background },\n ]}\n >\n <ActivityIndicator size=\"small\" color={secondaryTextColor} />\n <Text\n style={[\n styles.statusText,\n { color: secondaryTextColor, fontSize },\n ]}\n >\n Loading...\n </Text>\n </View>\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n position: \"relative\",\n width: \"100%\",\n },\n input: {\n width: \"100%\",\n paddingVertical: 10,\n paddingHorizontal: 12,\n borderWidth: 1,\n },\n list: {\n position: \"absolute\",\n top: \"100%\",\n left: 0,\n right: 0,\n marginTop: 4,\n borderWidth: 1,\n maxHeight: 240,\n zIndex: 50,\n shadowColor: \"#000\",\n shadowOffset: { width: 0, height: 2 },\n shadowOpacity: 0.1,\n shadowRadius: 4,\n elevation: 4,\n overflow: \"hidden\",\n },\n item: {\n paddingVertical: 10,\n paddingHorizontal: 12,\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n },\n name: {\n fontWeight: \"500\",\n flexShrink: 1,\n },\n province: {\n fontSize: 12,\n marginLeft: 8,\n },\n status: {\n position: \"absolute\",\n top: \"100%\",\n left: 0,\n right: 0,\n marginTop: 4,\n borderWidth: 1,\n padding: 10,\n flexDirection: \"row\",\n alignItems: \"center\",\n },\n statusText: {\n marginLeft: 8,\n },\n});\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { AutocompleteResult, PlacesLayerClient } from \"@placeslayer/sdk\";\n\nconst DEBOUNCE_MS = 300;\n\nexport interface AutocompleteState {\n results: AutocompleteResult[];\n loading: boolean;\n error: Error | null;\n}\n\nexport function useAutocomplete(\n client: PlacesLayerClient,\n countryCode: string,\n query: string,\n opts?: { limit?: number; debounceMs?: number }\n): AutocompleteState {\n const [results, setResults] = useState<AutocompleteResult[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const version = useRef(0);\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n const delay = opts?.debounceMs ?? DEBOUNCE_MS;\n\n const search = useCallback(() => {\n const trimmed = query.trim();\n if (!trimmed) {\n setResults([]);\n setLoading(false);\n return;\n }\n\n const current = ++version.current;\n setLoading(true);\n\n client\n .autocomplete(countryCode, trimmed, { limit: opts?.limit })\n .then((data) => {\n if (current === version.current) {\n setResults(data);\n setLoading(false);\n }\n })\n .catch((err) => {\n if (current === version.current) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n });\n }, [client, countryCode, query, opts?.limit]);\n\n useEffect(() => {\n if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(search, delay);\n return () => {\n if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n };\n }, [search, delay]);\n\n return { results, loading, error };\n}\n","import { useRef } from \"react\";\nimport { PlacesLayerClient, type PlacesLayerConfig } from \"@placeslayer/sdk\";\n\nexport function usePlacesLayerClient(config: PlacesLayerConfig): PlacesLayerClient {\n const ref = useRef<PlacesLayerClient | null>(null);\n\n if (!ref.current || (ref.current as unknown as { apiKey: string }).apiKey !== config.apiKey) {\n ref.current = new PlacesLayerClient(config);\n }\n\n return ref.current;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface AsyncState<T> {\n data: T | null;\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useAsync<T>(\n fn: () => Promise<T>,\n deps: unknown[]\n): AsyncState<T> {\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const version = useRef(0);\n\n const execute = useCallback(() => {\n const current = ++version.current;\n setLoading(true);\n setError(null);\n\n fn()\n .then((result) => {\n if (current === version.current) {\n setData(result);\n setLoading(false);\n }\n })\n .catch((err) => {\n if (current === version.current) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n\n useEffect(() => {\n execute();\n }, [execute]);\n\n return { data, loading, error, refetch: execute };\n}\n","import type { Country, PlacesLayerClient } from \"@placeslayer/sdk\";\nimport { type AsyncState, useAsync } from \"./useAsync\";\n\nexport function useCountries(client: PlacesLayerClient): AsyncState<Country[]> {\n return useAsync(() => client.countries(), [client]);\n}\n","import type { PlacesLayerClient, Province } from \"@placeslayer/sdk\";\nimport { type AsyncState, useAsync } from \"./useAsync\";\n\nexport function useProvinces(\n client: PlacesLayerClient,\n countryCode: string,\n opts?: { native?: boolean }\n): AsyncState<Province[]> {\n return useAsync(\n () => client.provinces(countryCode, opts),\n [client, countryCode, opts?.native]\n );\n}\n","import type { City, PlacesLayerClient } from \"@placeslayer/sdk\";\nimport { type AsyncState, useAsync } from \"./useAsync\";\n\nexport function useCities(\n client: PlacesLayerClient,\n countryCode: string,\n provinceCode: string,\n opts?: { native?: boolean }\n): AsyncState<City[]> {\n return useAsync(\n () => client.cities(countryCode, provinceCode, opts),\n [client, countryCode, provinceCode, opts?.native]\n );\n}\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useState","useRef","useState","useEffect","useCallback","useRef","PlacesLayerClient","useCallback","useEffect","useRef","useState"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@placeslayer/sdk-react-native",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React Native components and hooks for PlacesLayer",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"react-native": "./dist/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@placeslayer/sdk": "^0.1.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": ">=18",
|
|
35
|
+
"react-native": ">=0.71"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"placeslayer",
|
|
39
|
+
"react-native",
|
|
40
|
+
"geography",
|
|
41
|
+
"cities",
|
|
42
|
+
"countries",
|
|
43
|
+
"provinces",
|
|
44
|
+
"hooks"
|
|
45
|
+
],
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/hexar/placeslayer-sdk"
|
|
50
|
+
}
|
|
51
|
+
}
|