@kaisarsofi/react-native-phone-input 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.
Files changed (91) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +467 -0
  3. package/lib/commonjs/CountryPicker.js +669 -0
  4. package/lib/commonjs/CountryPicker.js.map +1 -0
  5. package/lib/commonjs/Flag.js +23 -0
  6. package/lib/commonjs/Flag.js.map +1 -0
  7. package/lib/commonjs/PhoneInput.js +252 -0
  8. package/lib/commonjs/PhoneInput.js.map +1 -0
  9. package/lib/commonjs/countries.js +75 -0
  10. package/lib/commonjs/countries.js.map +1 -0
  11. package/lib/commonjs/data/countries.json +1472 -0
  12. package/lib/commonjs/index.js +71 -0
  13. package/lib/commonjs/index.js.map +1 -0
  14. package/lib/commonjs/package.json +1 -0
  15. package/lib/commonjs/safeArea.js +44 -0
  16. package/lib/commonjs/safeArea.js.map +1 -0
  17. package/lib/commonjs/theme.js +85 -0
  18. package/lib/commonjs/theme.js.map +1 -0
  19. package/lib/commonjs/types.js +6 -0
  20. package/lib/commonjs/types.js.map +1 -0
  21. package/lib/commonjs/value.js +68 -0
  22. package/lib/commonjs/value.js.map +1 -0
  23. package/lib/module/CountryPicker.js +665 -0
  24. package/lib/module/CountryPicker.js.map +1 -0
  25. package/lib/module/Flag.js +19 -0
  26. package/lib/module/Flag.js.map +1 -0
  27. package/lib/module/PhoneInput.js +248 -0
  28. package/lib/module/PhoneInput.js.map +1 -0
  29. package/lib/module/countries.js +65 -0
  30. package/lib/module/countries.js.map +1 -0
  31. package/lib/module/data/countries.json +1472 -0
  32. package/lib/module/index.js +8 -0
  33. package/lib/module/index.js.map +1 -0
  34. package/lib/module/package.json +1 -0
  35. package/lib/module/safeArea.js +39 -0
  36. package/lib/module/safeArea.js.map +1 -0
  37. package/lib/module/theme.js +81 -0
  38. package/lib/module/theme.js.map +1 -0
  39. package/lib/module/types.js +4 -0
  40. package/lib/module/types.js.map +1 -0
  41. package/lib/module/value.js +62 -0
  42. package/lib/module/value.js.map +1 -0
  43. package/lib/typescript/commonjs/package.json +1 -0
  44. package/lib/typescript/commonjs/src/CountryPicker.d.ts +37 -0
  45. package/lib/typescript/commonjs/src/CountryPicker.d.ts.map +1 -0
  46. package/lib/typescript/commonjs/src/Flag.d.ts +10 -0
  47. package/lib/typescript/commonjs/src/Flag.d.ts.map +1 -0
  48. package/lib/typescript/commonjs/src/PhoneInput.d.ts +3 -0
  49. package/lib/typescript/commonjs/src/PhoneInput.d.ts.map +1 -0
  50. package/lib/typescript/commonjs/src/countries.d.ts +28 -0
  51. package/lib/typescript/commonjs/src/countries.d.ts.map +1 -0
  52. package/lib/typescript/commonjs/src/index.d.ts +9 -0
  53. package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
  54. package/lib/typescript/commonjs/src/safeArea.d.ts +11 -0
  55. package/lib/typescript/commonjs/src/safeArea.d.ts.map +1 -0
  56. package/lib/typescript/commonjs/src/theme.d.ts +53 -0
  57. package/lib/typescript/commonjs/src/theme.d.ts.map +1 -0
  58. package/lib/typescript/commonjs/src/types.d.ts +211 -0
  59. package/lib/typescript/commonjs/src/types.d.ts.map +1 -0
  60. package/lib/typescript/commonjs/src/value.d.ts +22 -0
  61. package/lib/typescript/commonjs/src/value.d.ts.map +1 -0
  62. package/lib/typescript/module/package.json +1 -0
  63. package/lib/typescript/module/src/CountryPicker.d.ts +37 -0
  64. package/lib/typescript/module/src/CountryPicker.d.ts.map +1 -0
  65. package/lib/typescript/module/src/Flag.d.ts +10 -0
  66. package/lib/typescript/module/src/Flag.d.ts.map +1 -0
  67. package/lib/typescript/module/src/PhoneInput.d.ts +3 -0
  68. package/lib/typescript/module/src/PhoneInput.d.ts.map +1 -0
  69. package/lib/typescript/module/src/countries.d.ts +28 -0
  70. package/lib/typescript/module/src/countries.d.ts.map +1 -0
  71. package/lib/typescript/module/src/index.d.ts +9 -0
  72. package/lib/typescript/module/src/index.d.ts.map +1 -0
  73. package/lib/typescript/module/src/safeArea.d.ts +11 -0
  74. package/lib/typescript/module/src/safeArea.d.ts.map +1 -0
  75. package/lib/typescript/module/src/theme.d.ts +53 -0
  76. package/lib/typescript/module/src/theme.d.ts.map +1 -0
  77. package/lib/typescript/module/src/types.d.ts +211 -0
  78. package/lib/typescript/module/src/types.d.ts.map +1 -0
  79. package/lib/typescript/module/src/value.d.ts +22 -0
  80. package/lib/typescript/module/src/value.d.ts.map +1 -0
  81. package/package.json +215 -0
  82. package/src/CountryPicker.tsx +805 -0
  83. package/src/Flag.tsx +17 -0
  84. package/src/PhoneInput.tsx +363 -0
  85. package/src/countries.ts +95 -0
  86. package/src/data/countries.json +1472 -0
  87. package/src/index.tsx +21 -0
  88. package/src/safeArea.ts +50 -0
  89. package/src/theme.ts +112 -0
  90. package/src/types.ts +229 -0
  91. package/src/value.ts +70 -0
@@ -0,0 +1,669 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.CountryPicker = CountryPicker;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _countries = require("./countries.js");
10
+ var _Flag = require("./Flag.js");
11
+ var _safeArea = require("./safeArea.js");
12
+ var _theme = require("./theme.js");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
15
+ const ROW_HEIGHT = 48;
16
+ const HEADER_HEIGHT = 28;
17
+ /** Width reserved on the right for the A-Z index, so list content and the
18
+ * search box stop at the same place instead of running under it. */
19
+ const SIDEBAR_GUTTER = 32;
20
+ function CountryPicker({
21
+ visible,
22
+ countries,
23
+ selected,
24
+ onSelect,
25
+ onClose,
26
+ searchPlaceholder = 'Search country or code',
27
+ disableSearch,
28
+ renderCountryItem,
29
+ palette: providedPalette,
30
+ colorScheme,
31
+ safeAreaInsets,
32
+ presentationStyle,
33
+ groupAlphabetically = true,
34
+ showAlphabetIndex = true,
35
+ styles: styleOverrides
36
+ }) {
37
+ const fallbackPalette = (0, _theme.usePhoneInputPalette)(colorScheme);
38
+ const palette = providedPalette ?? fallbackPalette;
39
+
40
+ // An explicit prop is applied as given; auto-detected insets are adjusted,
41
+ // because the ambient value describes the *window*, not this sheet. Inside
42
+ // an iOS pageSheet/formSheet the system has already inset the top, so
43
+ // adding the window's top inset there would push the header down by the
44
+ // notch height for no reason.
45
+ const ambientInsets = (0, _safeArea.useOptionalSafeAreaInsets)();
46
+ const isIosSheet = _reactNative.Platform.OS === 'ios' && (presentationStyle ?? 'pageSheet') !== 'fullScreen';
47
+
48
+ // Read edge by edge rather than depending on the prop object, which callers
49
+ // naturally write inline.
50
+ const insetTop = safeAreaInsets?.top;
51
+ const insetBottom = safeAreaInsets?.bottom;
52
+ const insetLeft = safeAreaInsets?.left;
53
+ const insetRight = safeAreaInsets?.right;
54
+ const insets = (0, _react.useMemo)(() => {
55
+ const auto = ambientInsets ? {
56
+ ...ambientInsets,
57
+ top: isIosSheet ? 0 : ambientInsets.top
58
+ } : null;
59
+ if (insetTop === undefined && insetBottom === undefined && insetLeft === undefined && insetRight === undefined) {
60
+ return auto;
61
+ }
62
+ // Each edge given explicitly wins; the rest still fall back to the
63
+ // detected value, so `{ top: 0 }` overrides only the top.
64
+ return {
65
+ top: insetTop ?? auto?.top ?? 0,
66
+ bottom: insetBottom ?? auto?.bottom ?? 0,
67
+ left: insetLeft ?? auto?.left ?? 0,
68
+ right: insetRight ?? auto?.right ?? 0
69
+ };
70
+ }, [insetTop, insetBottom, insetLeft, insetRight, ambientInsets, isIosSheet]);
71
+ const [query, setQuery] = (0, _react.useState)('');
72
+ const scrollRef = (0, _react.useRef)(null);
73
+ const sidebarRef = (0, _react.useRef)(null);
74
+ const [activeLetter, setActiveLetter] = (0, _react.useState)(null);
75
+ const sidebarLayout = (0, _react.useRef)({
76
+ y: 0,
77
+ height: 1
78
+ });
79
+ const hideActiveLetterTimer = (0, _react.useRef)(null);
80
+ (0, _react.useEffect)(() => () => {
81
+ if (hideActiveLetterTimer.current) {
82
+ clearTimeout(hideActiveLetterTimer.current);
83
+ }
84
+ }, []);
85
+
86
+ // Reset the search when the sheet closes, so reopening it starts on the
87
+ // full list rather than on whatever was typed last time.
88
+ (0, _react.useEffect)(() => {
89
+ if (!visible) setQuery('');
90
+ }, [visible]);
91
+ const showActiveLetterBriefly = letter => {
92
+ setActiveLetter(letter);
93
+ if (hideActiveLetterTimer.current) {
94
+ clearTimeout(hideActiveLetterTimer.current);
95
+ }
96
+ hideActiveLetterTimer.current = setTimeout(() => {
97
+ setActiveLetter(null);
98
+ hideActiveLetterTimer.current = null;
99
+ }, 600);
100
+ };
101
+
102
+ // Every color the picker draws lives here rather than in the StyleSheet
103
+ // below, which now holds layout only — the two hardcoded light values that
104
+ // used to sit in `row` and `search` were exactly what made dark mode
105
+ // unreadable (bright separator lines, a near-white search box).
106
+ const colors = (0, _react.useMemo)(() => ({
107
+ container: {
108
+ backgroundColor: palette.background
109
+ },
110
+ headerSection: {
111
+ backgroundColor: palette.background,
112
+ borderBottomColor: palette.border
113
+ },
114
+ searchContainer: {
115
+ backgroundColor: palette.surface,
116
+ borderColor: palette.border
117
+ },
118
+ search: {
119
+ color: palette.text
120
+ },
121
+ searchIcon: {
122
+ borderColor: palette.placeholder
123
+ },
124
+ searchIconFill: {
125
+ backgroundColor: palette.placeholder
126
+ },
127
+ handle: {
128
+ backgroundColor: palette.handle
129
+ },
130
+ title: {
131
+ color: palette.text
132
+ },
133
+ closeButton: {
134
+ backgroundColor: palette.surface
135
+ },
136
+ closeButtonPressed: {
137
+ backgroundColor: palette.surfacePressed
138
+ },
139
+ closeButtonText: {
140
+ color: palette.textMuted
141
+ },
142
+ rowSeparator: {
143
+ borderBottomColor: palette.border
144
+ },
145
+ rowSelected: {
146
+ backgroundColor: palette.surface
147
+ },
148
+ name: {
149
+ color: palette.text
150
+ },
151
+ dialCode: {
152
+ color: palette.textMuted
153
+ },
154
+ empty: {
155
+ color: palette.textMuted
156
+ },
157
+ sectionHeader: {
158
+ backgroundColor: palette.background
159
+ },
160
+ sectionHeaderText: {
161
+ color: palette.textMuted
162
+ },
163
+ sidebarLetter: {
164
+ color: palette.accent
165
+ },
166
+ sidebarLetterDisabled: {
167
+ color: palette.disabled
168
+ },
169
+ sidebarLetterActive: {
170
+ color: palette.accentContrast,
171
+ backgroundColor: palette.accent
172
+ },
173
+ bubble: {
174
+ backgroundColor: palette.bubble
175
+ },
176
+ bubbleText: {
177
+ color: palette.bubbleText
178
+ }
179
+ }), [palette]);
180
+ const filtered = (0, _react.useMemo)(() => {
181
+ const q = query.trim().toLowerCase();
182
+ if (!q) return countries;
183
+ return countries.filter(c => c.name.toLowerCase().includes(q) || c.dialCode.includes(q.replace(/^\+/, '')) || c.code.toLowerCase().includes(q));
184
+ }, [countries, query]);
185
+ const isSearching = !!query.trim();
186
+
187
+ // Countries are always grouped into A-Z chunks (unless searching) so the
188
+ // alphabet index has something to jump to even when section headers
189
+ // (groupAlphabetically) are turned off — the two are independent: headers
190
+ // control what's visible in the list, the index controls the sidebar.
191
+ // `filtered` is the `countries` array itself whenever the query is empty,
192
+ // so the unsearched sections and the sidebar's letters are the same grouping
193
+ // — computed once here instead of twice.
194
+ const allSections = (0, _react.useMemo)(() => (0, _countries.groupCountriesByLetter)(countries), [countries]);
195
+ const sections = (0, _react.useMemo)(() => isSearching ? [{
196
+ letter: '',
197
+ data: filtered
198
+ }] : allSections, [filtered, isSearching, allSections]);
199
+ const showHeaders = groupAlphabetically && !isSearching;
200
+
201
+ // Whether the A-Z gutter is reserved is deliberately independent of the
202
+ // query: the sidebar itself hides while searching, but the space stays
203
+ // claimed so the search box and the list don't jump wider mid-keystroke.
204
+ const reservesSidebar = showAlphabetIndex && allSections.length > 3;
205
+ const showSidebar = reservesSidebar && !isSearching;
206
+ const availableLetters = (0, _react.useMemo)(() => new Set(sections.map(s => s.letter)), [sections]);
207
+
208
+ // Offset of each section header, computed from fixed row/header heights
209
+ // (styles.row / styles.sectionHeader below) rather than measured via
210
+ // onLayout — this content isn't virtualized, so the analytical offset is
211
+ // exact and avoids relying on layout events firing through the Modal.
212
+ const sectionOffsets = (0, _react.useMemo)(() => {
213
+ const offsets = {};
214
+ let offset = 0;
215
+ for (const section of sections) {
216
+ offsets[section.letter] = offset;
217
+ if (showHeaders && section.letter) offset += HEADER_HEIGHT;
218
+ offset += section.data.length * ROW_HEIGHT;
219
+ }
220
+ return offsets;
221
+ }, [sections, showHeaders]);
222
+ const jumpToLetter = letter => {
223
+ let target = letter;
224
+ if (!availableLetters.has(target)) {
225
+ const forward = ALPHABET.find(l => l >= letter && availableLetters.has(l));
226
+ const fallback = forward ?? [...ALPHABET].reverse().find(l => availableLetters.has(l));
227
+ if (!fallback) return;
228
+ target = fallback;
229
+ }
230
+ showActiveLetterBriefly(target);
231
+ const offset = sectionOffsets[target];
232
+ if (offset === undefined) return;
233
+ scrollRef.current?.scrollTo({
234
+ y: offset,
235
+ animated: false
236
+ });
237
+ };
238
+ const jumpFromPageY = pageY => {
239
+ const {
240
+ height
241
+ } = sidebarLayout.current;
242
+ const y = pageY - sidebarLayout.current.y;
243
+ const idx = Math.min(ALPHABET.length - 1, Math.max(0, Math.floor(y / height * ALPHABET.length)));
244
+ const letter = ALPHABET[idx];
245
+ if (letter) jumpToLetter(letter);
246
+ };
247
+ const handleSidebarTouch = evt => {
248
+ jumpFromPageY(evt.nativeEvent.pageY);
249
+ };
250
+
251
+ // react-native-web doesn't synthesize touch events from mouse input, so
252
+ // onTouchStart/onTouchMove alone never fire for a mouse click/drag — the
253
+ // sidebar would be entirely inert on web without these. isMouseDown tracks
254
+ // whether the button is held, mirroring onTouchMove's "only while touching"
255
+ // behavior, since onMouseMove otherwise fires on hover too.
256
+ const isMouseDown = (0, _react.useRef)(false);
257
+ const webMouseHandlers = _reactNative.Platform.OS === 'web' ? {
258
+ onMouseDown: e => {
259
+ isMouseDown.current = true;
260
+ jumpFromPageY(e.nativeEvent.pageY);
261
+ },
262
+ onMouseMove: e => {
263
+ if (isMouseDown.current) jumpFromPageY(e.nativeEvent.pageY);
264
+ },
265
+ onMouseUp: () => {
266
+ isMouseDown.current = false;
267
+ if (hideActiveLetterTimer.current) {
268
+ clearTimeout(hideActiveLetterTimer.current);
269
+ hideActiveLetterTimer.current = null;
270
+ }
271
+ setActiveLetter(null);
272
+ }
273
+ } : {};
274
+ const renderRow = (0, _react.useCallback)(item => {
275
+ const isSelected = item.code === selected?.code;
276
+ const onPress = () => onSelect(item);
277
+ if (renderCountryItem) {
278
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
279
+ children: renderCountryItem({
280
+ item,
281
+ isSelected,
282
+ onPress
283
+ })
284
+ });
285
+ }
286
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
287
+ onPress: onPress,
288
+ style: ({
289
+ pressed
290
+ }) => [styles.row, styleOverrides?.row, isSelected && [styles.rowSelected, colors.rowSelected, styleOverrides?.rowSelected], pressed && styles.rowPressed],
291
+ accessibilityRole: "button",
292
+ accessibilityLabel: `${item.name}, +${item.dialCode}`,
293
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
294
+ style: [styles.rowInner, colors.rowSeparator],
295
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Flag.Flag, {
296
+ country: item,
297
+ size: 22,
298
+ style: [styles.flag, styleOverrides?.flag]
299
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
300
+ style: [styles.name, colors.name, styleOverrides?.name],
301
+ numberOfLines: 1,
302
+ children: item.name
303
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
304
+ style: [styles.dialCode, colors.dialCode, styleOverrides?.dialCode],
305
+ children: ["+", item.dialCode]
306
+ })]
307
+ })
308
+ });
309
+ }, [selected, onSelect, renderCountryItem, styleOverrides, colors]);
310
+
311
+ // Memoized separately from the rest of the tree so sidebar drag-scrubbing
312
+ // (which updates `activeLetter` on every touch move) doesn't force React to
313
+ // re-render every one of the ~240 rows on each frame — only the sidebar and
314
+ // the letter bubble, which actually depend on `activeLetter`, do.
315
+ const listContent = (0, _react.useMemo)(() => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
316
+ children: [filtered.length === 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
317
+ style: [styles.empty, colors.empty],
318
+ children: "No countries found"
319
+ }), sections.map(section => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
320
+ children: [showHeaders && section.letter ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
321
+ style: [styles.sectionHeader, colors.sectionHeader, styleOverrides?.sectionHeader],
322
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
323
+ style: [styles.sectionHeaderText, colors.sectionHeaderText, styleOverrides?.sectionHeaderText],
324
+ children: section.letter
325
+ })
326
+ }) : null, section.data.map(item => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
327
+ children: renderRow(item)
328
+ }, item.code))]
329
+ }, section.letter || 'all'))]
330
+ }), [filtered.length, sections, showHeaders, styleOverrides, renderRow, colors]);
331
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
332
+ visible: visible,
333
+ animationType: "slide",
334
+ onRequestClose: onClose,
335
+ presentationStyle: _reactNative.Platform.OS === 'ios' ? presentationStyle ?? 'pageSheet' : undefined,
336
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
337
+ style: [styles.container, colors.container, insets ? {
338
+ paddingTop: insets.top,
339
+ paddingBottom: insets.bottom,
340
+ paddingLeft: insets.left,
341
+ paddingRight: insets.right
342
+ } : null, styleOverrides?.container],
343
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
344
+ style: [styles.headerSection, colors.headerSection, styleOverrides?.headerSection],
345
+ children: [_reactNative.Platform.OS === 'ios' && (presentationStyle ?? 'pageSheet') === 'pageSheet' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
346
+ style: styles.dragHandleWrap,
347
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
348
+ style: [styles.dragHandle, colors.handle]
349
+ })
350
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
351
+ style: [styles.header, styleOverrides?.header],
352
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
353
+ style: [styles.title, colors.title, styleOverrides?.title],
354
+ children: "Select a country"
355
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
356
+ onPress: onClose,
357
+ hitSlop: 8,
358
+ style: ({
359
+ pressed
360
+ }) => [styles.closeButton, colors.closeButton, styleOverrides?.closeButton, pressed && colors.closeButtonPressed],
361
+ accessibilityRole: "button",
362
+ accessibilityLabel: "Close",
363
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
364
+ style: [styles.closeButtonText, colors.closeButtonText, styleOverrides?.closeButtonText],
365
+ children: "\u2715"
366
+ })
367
+ })]
368
+ }), !disableSearch && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
369
+ style: [styles.searchContainer, reservesSidebar ? styles.searchWithJump : null, colors.searchContainer, styleOverrides?.searchContainer],
370
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
371
+ style: styles.searchIcon,
372
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
373
+ style: [styles.searchIconLens, colors.searchIcon]
374
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
375
+ style: [styles.searchIconHandle, colors.searchIconFill]
376
+ })]
377
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
378
+ value: query,
379
+ onChangeText: setQuery,
380
+ placeholder: searchPlaceholder,
381
+ placeholderTextColor: palette.placeholder,
382
+ style: [styles.search, colors.search, styleOverrides?.search],
383
+ autoCorrect: false,
384
+ autoCapitalize: "none",
385
+ clearButtonMode: "while-editing"
386
+ })]
387
+ })]
388
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
389
+ style: styles.body,
390
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
391
+ ref: scrollRef,
392
+ keyboardShouldPersistTaps: "handled"
393
+ // Fallback for when no insets are available: UIKit adds the bottom
394
+ // inset itself, since the scroll view's frame overlaps the home
395
+ // indicator but not the notch. Turned off once we pad the
396
+ // container ourselves, so the two don't stack.
397
+ ,
398
+ contentInsetAdjustmentBehavior: insets?.bottom ? 'never' : 'automatic',
399
+ contentContainerStyle: reservesSidebar ? styles.listContentWithJump : undefined,
400
+ children: listContent
401
+ }), showSidebar && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
402
+ ref: sidebarRef,
403
+ style: styles.sidebar,
404
+ onLayout: () => {
405
+ // Measure the sidebar's absolute position in the same
406
+ // coordinate space as touch events' `pageY` (measure()'s
407
+ // pageY, not measureInWindow()'s window-relative y, which can
408
+ // differ by the status bar/safe-area inset) rather than
409
+ // deriving it from `pageY - locationY` on each touch — that
410
+ // calculation is unreliable once the touch target is a nested
411
+ // child (each letter below), which was causing the touch
412
+ // position -> letter mapping to drift, sometimes badly enough
413
+ // to land several letters off (e.g. tapping "M" landing on "P").
414
+ sidebarRef.current?.measure((_x, _y, _w, height, _px, pageY) => {
415
+ sidebarLayout.current = {
416
+ y: pageY,
417
+ height
418
+ };
419
+ });
420
+ },
421
+ onTouchStart: handleSidebarTouch,
422
+ onTouchMove: handleSidebarTouch,
423
+ onTouchEnd: () => {
424
+ if (hideActiveLetterTimer.current) {
425
+ clearTimeout(hideActiveLetterTimer.current);
426
+ hideActiveLetterTimer.current = null;
427
+ }
428
+ setActiveLetter(null);
429
+ },
430
+ ...webMouseHandlers,
431
+ children: ALPHABET.map(letter => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
432
+ style: styles.sidebarLetterTouchable,
433
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
434
+ style: [styles.sidebarLetter, colors.sidebarLetter, styleOverrides?.sidebarLetter, !availableLetters.has(letter) && colors.sidebarLetterDisabled, activeLetter === letter && [styles.sidebarLetterActive, colors.sidebarLetterActive, styleOverrides?.sidebarLetterActive]],
435
+ children: letter
436
+ })
437
+ }, letter))
438
+ }), activeLetter && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
439
+ style: [styles.bubble, colors.bubble],
440
+ pointerEvents: "none",
441
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
442
+ style: [styles.bubbleText, colors.bubbleText],
443
+ children: activeLetter
444
+ })
445
+ })]
446
+ })]
447
+ })
448
+ });
449
+ }
450
+ const styles = _reactNative.StyleSheet.create({
451
+ container: {
452
+ flex: 1
453
+ },
454
+ dragHandleWrap: {
455
+ alignItems: 'center',
456
+ paddingTop: 8
457
+ },
458
+ dragHandle: {
459
+ width: 36,
460
+ height: 5,
461
+ borderRadius: 3
462
+ },
463
+ // Title + search read as one block, ruled off from the list, instead of the
464
+ // search floating loose above the first section band.
465
+ headerSection: {
466
+ borderBottomWidth: _reactNative.StyleSheet.hairlineWidth,
467
+ // Sits above the list so the shadow falls over the rows scrolling beneath
468
+ // it, which separates the two far better than a line on its own.
469
+ zIndex: 1,
470
+ ..._reactNative.Platform.select({
471
+ ios: {
472
+ shadowColor: '#000',
473
+ shadowOpacity: 0.06,
474
+ shadowRadius: 8,
475
+ shadowOffset: {
476
+ width: 0,
477
+ height: 3
478
+ }
479
+ },
480
+ android: {
481
+ elevation: 3
482
+ },
483
+ default: {}
484
+ })
485
+ },
486
+ header: {
487
+ flexDirection: 'row',
488
+ alignItems: 'center',
489
+ justifyContent: 'space-between',
490
+ paddingHorizontal: 20,
491
+ paddingTop: 16,
492
+ paddingBottom: 16
493
+ },
494
+ title: {
495
+ fontSize: 20,
496
+ fontWeight: '700'
497
+ },
498
+ closeButton: {
499
+ width: 30,
500
+ height: 30,
501
+ borderRadius: 15,
502
+ alignItems: 'center',
503
+ justifyContent: 'center'
504
+ },
505
+ closeButtonText: {
506
+ fontSize: 14,
507
+ fontWeight: '700'
508
+ },
509
+ searchContainer: {
510
+ flexDirection: 'row',
511
+ alignItems: 'center',
512
+ gap: 8,
513
+ marginLeft: 20,
514
+ marginRight: 20,
515
+ marginBottom: 14,
516
+ paddingHorizontal: 12,
517
+ height: 48,
518
+ borderRadius: 12,
519
+ borderWidth: _reactNative.StyleSheet.hairlineWidth
520
+ },
521
+ searchIcon: {
522
+ width: 15,
523
+ height: 15,
524
+ alignItems: 'center',
525
+ justifyContent: 'center'
526
+ },
527
+ searchIconLens: {
528
+ width: 11,
529
+ height: 11,
530
+ borderRadius: 6,
531
+ borderWidth: 1.5,
532
+ position: 'absolute',
533
+ top: 0,
534
+ left: 0
535
+ },
536
+ searchIconHandle: {
537
+ position: 'absolute',
538
+ width: 1.5,
539
+ height: 6,
540
+ borderRadius: 1,
541
+ right: 1,
542
+ bottom: 0,
543
+ transform: [{
544
+ rotate: '-45deg'
545
+ }]
546
+ },
547
+ search: {
548
+ flex: 1,
549
+ minWidth: 0,
550
+ paddingVertical: 0,
551
+ fontSize: 16,
552
+ ...(_reactNative.Platform.OS === 'web' ? {
553
+ outlineStyle: 'none'
554
+ } : null),
555
+ ...(_reactNative.Platform.OS === 'android' ? {
556
+ textAlignVertical: 'center',
557
+ includeFontPadding: false
558
+ } : null)
559
+ },
560
+ searchWithJump: {
561
+ marginRight: 20 + SIDEBAR_GUTTER
562
+ },
563
+ body: {
564
+ flex: 1,
565
+ flexDirection: 'row'
566
+ },
567
+ listContentWithJump: {
568
+ paddingRight: SIDEBAR_GUTTER
569
+ },
570
+ row: {
571
+ height: ROW_HEIGHT,
572
+ flexDirection: 'row',
573
+ paddingLeft: 20
574
+ },
575
+ rowInner: {
576
+ flex: 1,
577
+ flexDirection: 'row',
578
+ alignItems: 'center',
579
+ paddingRight: 20,
580
+ gap: 12,
581
+ borderBottomWidth: _reactNative.StyleSheet.hairlineWidth
582
+ },
583
+ rowSelected: {},
584
+ rowPressed: {
585
+ opacity: 0.6
586
+ },
587
+ flag: {
588
+ marginRight: 0
589
+ },
590
+ name: {
591
+ flex: 1,
592
+ fontSize: 16
593
+ },
594
+ dialCode: {
595
+ fontSize: 16
596
+ },
597
+ empty: {
598
+ textAlign: 'center',
599
+ marginTop: 32
600
+ },
601
+ sectionHeader: {
602
+ height: HEADER_HEIGHT,
603
+ justifyContent: 'flex-end',
604
+ paddingHorizontal: 20,
605
+ paddingBottom: 4
606
+ },
607
+ sectionHeaderText: {
608
+ fontSize: 12,
609
+ fontWeight: '700',
610
+ letterSpacing: 0.6
611
+ },
612
+ sidebar: {
613
+ // Inset from top/bottom (rather than padding the content box) so the
614
+ // measured height used by handleSidebarTouch still matches exactly what
615
+ // the flex:1 letters occupy — padding would leave the touch math using a
616
+ // taller box than the letters actually fill, reintroducing the same
617
+ // mismatch fixed above. The larger bottom inset keeps "Z" clear of the
618
+ // safe-area edge/home indicator instead of sitting flush against it.
619
+ position: 'absolute',
620
+ // On web, a visible OS scrollbar can render on top of a sidebar pinned
621
+ // to the very edge (browsers reserve/overlay scrollbar width there),
622
+ // eating its clicks — inset it clear of that on web specifically.
623
+ right: _reactNative.Platform.OS === 'web' ? 14 : 0,
624
+ top: 8,
625
+ bottom: 28,
626
+ width: 28,
627
+ alignItems: 'stretch',
628
+ ...(_reactNative.Platform.OS === 'web' ? {
629
+ cursor: 'pointer'
630
+ } : null)
631
+ },
632
+ // Each letter gets an equal flex share of the sidebar's full height, so
633
+ // its rendered slice matches exactly what `handleSidebarTouch` computes
634
+ // (`floor((y / height) * 26)`) — with the old center-packed layout, the 26
635
+ // letters occupied a short block in the middle of a much taller container,
636
+ // so a touch position mapped from the FULL height landed on the wrong
637
+ // letter (sometimes several letters off) almost everywhere except near the
638
+ // exact center.
639
+ sidebarLetterTouchable: {
640
+ flex: 1,
641
+ alignItems: 'center',
642
+ justifyContent: 'center'
643
+ },
644
+ sidebarLetter: {
645
+ fontSize: 12,
646
+ fontWeight: '600',
647
+ lineHeight: _reactNative.Platform.OS === 'ios' ? 15 : 17,
648
+ textAlign: 'center'
649
+ },
650
+ sidebarLetterActive: {
651
+ borderRadius: 6,
652
+ overflow: 'hidden'
653
+ },
654
+ bubble: {
655
+ position: 'absolute',
656
+ right: 40,
657
+ top: '45%',
658
+ width: 64,
659
+ height: 64,
660
+ borderRadius: 32,
661
+ alignItems: 'center',
662
+ justifyContent: 'center'
663
+ },
664
+ bubbleText: {
665
+ fontSize: 28,
666
+ fontWeight: '700'
667
+ }
668
+ });
669
+ //# sourceMappingURL=CountryPicker.js.map