@mrmeg/expo-ui 0.25.0 → 0.26.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/CHANGELOG.md +118 -0
- package/LLM_USAGE.md +13 -1
- package/README.md +89 -18
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/BottomSheet.d.ts +1 -1
- package/dist/components/BottomSheet.js +28 -51
- package/dist/components/DismissKeyboard.d.ts +8 -6
- package/dist/components/DismissKeyboard.js +9 -7
- package/dist/components/DropdownMenu.js +1 -1
- package/dist/components/Icon.d.ts +24 -22
- package/dist/components/Icon.js +26 -3
- package/dist/components/Notification.js +3 -3
- package/dist/components/Popover.js +1 -1
- package/dist/components/Select.js +1 -1
- package/dist/components/StatCard.d.ts +1 -1
- package/dist/components/TextInput.d.ts +1 -1
- package/dist/components/TextInput.js +55 -35
- package/dist/components/Tooltip.js +1 -1
- package/dist/components/iconRegistry.generated.d.ts +148 -0
- package/dist/components/iconRegistry.generated.js +296 -0
- package/dist/components/keyboardDismiss.js +126 -55
- package/dist/components/keyboardFocusRegistry.d.ts +14 -4
- package/dist/components/keyboardFocusRegistry.js +13 -5
- package/dist/components/nativeTextField.android.d.ts +3 -0
- package/dist/components/nativeTextField.android.js +226 -0
- package/dist/components/nativeTextField.d.ts +14 -0
- package/dist/components/nativeTextField.js +14 -0
- package/dist/constants/spacing.d.ts +2 -2
- package/dist/constants/spacing.js +2 -2
- package/dist/design-system.json +152 -3
- package/dist/hooks/useResources.d.ts +5 -18
- package/dist/hooks/useResources.js +5 -43
- package/dist/state/themeStore.d.ts +1 -1
- package/llms-full.md +3 -3
- package/package.json +4 -3
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// GENERATED FILE - do not edit. Rebuild with `bun run ui:icons`.
|
|
3
|
+
// Source: icon-names.json (145 icons). Each icon is a
|
|
4
|
+
// per-glyph subpath import so consumers bundle only the names listed there.
|
|
5
|
+
import Archive from "lucide-react-native/icons/archive";
|
|
6
|
+
import ArrowDown from "lucide-react-native/icons/arrow-down";
|
|
7
|
+
import ArrowLeft from "lucide-react-native/icons/arrow-left";
|
|
8
|
+
import ArrowRight from "lucide-react-native/icons/arrow-right";
|
|
9
|
+
import ArrowUp from "lucide-react-native/icons/arrow-up";
|
|
10
|
+
import Award from "lucide-react-native/icons/award";
|
|
11
|
+
import Beer from "lucide-react-native/icons/beer";
|
|
12
|
+
import Bell from "lucide-react-native/icons/bell";
|
|
13
|
+
import BellOff from "lucide-react-native/icons/bell-off";
|
|
14
|
+
import Bold from "lucide-react-native/icons/bold";
|
|
15
|
+
import Bookmark from "lucide-react-native/icons/bookmark";
|
|
16
|
+
import Box from "lucide-react-native/icons/box";
|
|
17
|
+
import Briefcase from "lucide-react-native/icons/briefcase";
|
|
18
|
+
import Calendar from "lucide-react-native/icons/calendar";
|
|
19
|
+
import CalendarCheck from "lucide-react-native/icons/calendar-check";
|
|
20
|
+
import Camera from "lucide-react-native/icons/camera";
|
|
21
|
+
import CameraOff from "lucide-react-native/icons/camera-off";
|
|
22
|
+
import ChartNoAxesColumn from "lucide-react-native/icons/chart-no-axes-column";
|
|
23
|
+
import Check from "lucide-react-native/icons/check";
|
|
24
|
+
import ChevronDown from "lucide-react-native/icons/chevron-down";
|
|
25
|
+
import ChevronLeft from "lucide-react-native/icons/chevron-left";
|
|
26
|
+
import ChevronRight from "lucide-react-native/icons/chevron-right";
|
|
27
|
+
import ChevronUp from "lucide-react-native/icons/chevron-up";
|
|
28
|
+
import CircleAlert from "lucide-react-native/icons/circle-alert";
|
|
29
|
+
import CircleCheckBig from "lucide-react-native/icons/circle-check-big";
|
|
30
|
+
import CirclePlus from "lucide-react-native/icons/circle-plus";
|
|
31
|
+
import CircleQuestionMark from "lucide-react-native/icons/circle-question-mark";
|
|
32
|
+
import CircleStop from "lucide-react-native/icons/circle-stop";
|
|
33
|
+
import CircleX from "lucide-react-native/icons/circle-x";
|
|
34
|
+
import Clock from "lucide-react-native/icons/clock";
|
|
35
|
+
import Cloud from "lucide-react-native/icons/cloud";
|
|
36
|
+
import CloudOff from "lucide-react-native/icons/cloud-off";
|
|
37
|
+
import Code from "lucide-react-native/icons/code";
|
|
38
|
+
import Coffee from "lucide-react-native/icons/coffee";
|
|
39
|
+
import Compass from "lucide-react-native/icons/compass";
|
|
40
|
+
import Copy from "lucide-react-native/icons/copy";
|
|
41
|
+
import CreditCard from "lucide-react-native/icons/credit-card";
|
|
42
|
+
import Crosshair from "lucide-react-native/icons/crosshair";
|
|
43
|
+
import Database from "lucide-react-native/icons/database";
|
|
44
|
+
import DollarSign from "lucide-react-native/icons/dollar-sign";
|
|
45
|
+
import Download from "lucide-react-native/icons/download";
|
|
46
|
+
import Droplet from "lucide-react-native/icons/droplet";
|
|
47
|
+
import EllipsisVertical from "lucide-react-native/icons/ellipsis-vertical";
|
|
48
|
+
import ExternalLink from "lucide-react-native/icons/external-link";
|
|
49
|
+
import Eye from "lucide-react-native/icons/eye";
|
|
50
|
+
import EyeOff from "lucide-react-native/icons/eye-off";
|
|
51
|
+
import FaceSlightlySmiling from "lucide-react-native/icons/face-slightly-smiling";
|
|
52
|
+
import File from "lucide-react-native/icons/file";
|
|
53
|
+
import FileText from "lucide-react-native/icons/file-text";
|
|
54
|
+
import Folder from "lucide-react-native/icons/folder";
|
|
55
|
+
import Gift from "lucide-react-native/icons/gift";
|
|
56
|
+
import GitBranch from "lucide-react-native/icons/git-branch";
|
|
57
|
+
import GitPullRequest from "lucide-react-native/icons/git-pull-request";
|
|
58
|
+
import Globe from "lucide-react-native/icons/globe";
|
|
59
|
+
import Grid3x3 from "lucide-react-native/icons/grid-3x3";
|
|
60
|
+
import Hash from "lucide-react-native/icons/hash";
|
|
61
|
+
import Heart from "lucide-react-native/icons/heart";
|
|
62
|
+
import Hexagon from "lucide-react-native/icons/hexagon";
|
|
63
|
+
import House from "lucide-react-native/icons/house";
|
|
64
|
+
import Image from "lucide-react-native/icons/image";
|
|
65
|
+
import Inbox from "lucide-react-native/icons/inbox";
|
|
66
|
+
import Info from "lucide-react-native/icons/info";
|
|
67
|
+
import Italic from "lucide-react-native/icons/italic";
|
|
68
|
+
import Key from "lucide-react-native/icons/key";
|
|
69
|
+
import Layers from "lucide-react-native/icons/layers";
|
|
70
|
+
import LayoutGrid from "lucide-react-native/icons/layout-grid";
|
|
71
|
+
import Link from "lucide-react-native/icons/link";
|
|
72
|
+
import Link2 from "lucide-react-native/icons/link-2";
|
|
73
|
+
import List from "lucide-react-native/icons/list";
|
|
74
|
+
import Loader from "lucide-react-native/icons/loader";
|
|
75
|
+
import Lock from "lucide-react-native/icons/lock";
|
|
76
|
+
import LockOpen from "lucide-react-native/icons/lock-open";
|
|
77
|
+
import LogIn from "lucide-react-native/icons/log-in";
|
|
78
|
+
import LogOut from "lucide-react-native/icons/log-out";
|
|
79
|
+
import Mail from "lucide-react-native/icons/mail";
|
|
80
|
+
import Map from "lucide-react-native/icons/map";
|
|
81
|
+
import MapPin from "lucide-react-native/icons/map-pin";
|
|
82
|
+
import Maximize from "lucide-react-native/icons/maximize";
|
|
83
|
+
import Maximize2 from "lucide-react-native/icons/maximize-2";
|
|
84
|
+
import Menu from "lucide-react-native/icons/menu";
|
|
85
|
+
import MessageCircle from "lucide-react-native/icons/message-circle";
|
|
86
|
+
import MessageSquare from "lucide-react-native/icons/message-square";
|
|
87
|
+
import Mic from "lucide-react-native/icons/mic";
|
|
88
|
+
import MicOff from "lucide-react-native/icons/mic-off";
|
|
89
|
+
import Minus from "lucide-react-native/icons/minus";
|
|
90
|
+
import Monitor from "lucide-react-native/icons/monitor";
|
|
91
|
+
import Moon from "lucide-react-native/icons/moon";
|
|
92
|
+
import Music from "lucide-react-native/icons/music";
|
|
93
|
+
import Navigation from "lucide-react-native/icons/navigation";
|
|
94
|
+
import Package from "lucide-react-native/icons/package";
|
|
95
|
+
import PanelLeft from "lucide-react-native/icons/panel-left";
|
|
96
|
+
import PanelsTopLeft from "lucide-react-native/icons/panels-top-left";
|
|
97
|
+
import PartyPopper from "lucide-react-native/icons/party-popper";
|
|
98
|
+
import Pen from "lucide-react-native/icons/pen";
|
|
99
|
+
import PenLine from "lucide-react-native/icons/pen-line";
|
|
100
|
+
import Pencil from "lucide-react-native/icons/pencil";
|
|
101
|
+
import Percent from "lucide-react-native/icons/percent";
|
|
102
|
+
import Phone from "lucide-react-native/icons/phone";
|
|
103
|
+
import Play from "lucide-react-native/icons/play";
|
|
104
|
+
import Plus from "lucide-react-native/icons/plus";
|
|
105
|
+
import Printer from "lucide-react-native/icons/printer";
|
|
106
|
+
import Radio from "lucide-react-native/icons/radio";
|
|
107
|
+
import RefreshCw from "lucide-react-native/icons/refresh-cw";
|
|
108
|
+
import Repeat from "lucide-react-native/icons/repeat";
|
|
109
|
+
import RotateCw from "lucide-react-native/icons/rotate-cw";
|
|
110
|
+
import Search from "lucide-react-native/icons/search";
|
|
111
|
+
import Send from "lucide-react-native/icons/send";
|
|
112
|
+
import Server from "lucide-react-native/icons/server";
|
|
113
|
+
import Settings from "lucide-react-native/icons/settings";
|
|
114
|
+
import Share from "lucide-react-native/icons/share";
|
|
115
|
+
import Share2 from "lucide-react-native/icons/share-2";
|
|
116
|
+
import Shield from "lucide-react-native/icons/shield";
|
|
117
|
+
import ShieldOff from "lucide-react-native/icons/shield-off";
|
|
118
|
+
import ShoppingBag from "lucide-react-native/icons/shopping-bag";
|
|
119
|
+
import ShoppingCart from "lucide-react-native/icons/shopping-cart";
|
|
120
|
+
import Shuffle from "lucide-react-native/icons/shuffle";
|
|
121
|
+
import Slash from "lucide-react-native/icons/slash";
|
|
122
|
+
import SlidersHorizontal from "lucide-react-native/icons/sliders-horizontal";
|
|
123
|
+
import SlidersVertical from "lucide-react-native/icons/sliders-vertical";
|
|
124
|
+
import Smartphone from "lucide-react-native/icons/smartphone";
|
|
125
|
+
import Square from "lucide-react-native/icons/square";
|
|
126
|
+
import SquareCheckBig from "lucide-react-native/icons/square-check-big";
|
|
127
|
+
import SquarePen from "lucide-react-native/icons/square-pen";
|
|
128
|
+
import Star from "lucide-react-native/icons/star";
|
|
129
|
+
import Sun from "lucide-react-native/icons/sun";
|
|
130
|
+
import Tag from "lucide-react-native/icons/tag";
|
|
131
|
+
import Terminal from "lucide-react-native/icons/terminal";
|
|
132
|
+
import ThumbsUp from "lucide-react-native/icons/thumbs-up";
|
|
133
|
+
import Trash from "lucide-react-native/icons/trash";
|
|
134
|
+
import TrendingDown from "lucide-react-native/icons/trending-down";
|
|
135
|
+
import TrendingUp from "lucide-react-native/icons/trending-up";
|
|
136
|
+
import TriangleAlert from "lucide-react-native/icons/triangle-alert";
|
|
137
|
+
import Truck from "lucide-react-native/icons/truck";
|
|
138
|
+
import Underline from "lucide-react-native/icons/underline";
|
|
139
|
+
import Upload from "lucide-react-native/icons/upload";
|
|
140
|
+
import User from "lucide-react-native/icons/user";
|
|
141
|
+
import UserCheck from "lucide-react-native/icons/user-check";
|
|
142
|
+
import UserMinus from "lucide-react-native/icons/user-minus";
|
|
143
|
+
import UserPlus from "lucide-react-native/icons/user-plus";
|
|
144
|
+
import Users from "lucide-react-native/icons/users";
|
|
145
|
+
import Video from "lucide-react-native/icons/video";
|
|
146
|
+
import WifiOff from "lucide-react-native/icons/wifi-off";
|
|
147
|
+
import Wrench from "lucide-react-native/icons/wrench";
|
|
148
|
+
import X from "lucide-react-native/icons/x";
|
|
149
|
+
import Zap from "lucide-react-native/icons/zap";
|
|
150
|
+
export const ICONS = {
|
|
151
|
+
"archive": Archive,
|
|
152
|
+
"arrow-down": ArrowDown,
|
|
153
|
+
"arrow-left": ArrowLeft,
|
|
154
|
+
"arrow-right": ArrowRight,
|
|
155
|
+
"arrow-up": ArrowUp,
|
|
156
|
+
"award": Award,
|
|
157
|
+
"beer": Beer,
|
|
158
|
+
"bell": Bell,
|
|
159
|
+
"bell-off": BellOff,
|
|
160
|
+
"bold": Bold,
|
|
161
|
+
"bookmark": Bookmark,
|
|
162
|
+
"box": Box,
|
|
163
|
+
"briefcase": Briefcase,
|
|
164
|
+
"calendar": Calendar,
|
|
165
|
+
"calendar-check": CalendarCheck,
|
|
166
|
+
"camera": Camera,
|
|
167
|
+
"camera-off": CameraOff,
|
|
168
|
+
"chart-no-axes-column": ChartNoAxesColumn,
|
|
169
|
+
"check": Check,
|
|
170
|
+
"chevron-down": ChevronDown,
|
|
171
|
+
"chevron-left": ChevronLeft,
|
|
172
|
+
"chevron-right": ChevronRight,
|
|
173
|
+
"chevron-up": ChevronUp,
|
|
174
|
+
"circle-alert": CircleAlert,
|
|
175
|
+
"circle-check-big": CircleCheckBig,
|
|
176
|
+
"circle-plus": CirclePlus,
|
|
177
|
+
"circle-question-mark": CircleQuestionMark,
|
|
178
|
+
"circle-stop": CircleStop,
|
|
179
|
+
"circle-x": CircleX,
|
|
180
|
+
"clock": Clock,
|
|
181
|
+
"cloud": Cloud,
|
|
182
|
+
"cloud-off": CloudOff,
|
|
183
|
+
"code": Code,
|
|
184
|
+
"coffee": Coffee,
|
|
185
|
+
"compass": Compass,
|
|
186
|
+
"copy": Copy,
|
|
187
|
+
"credit-card": CreditCard,
|
|
188
|
+
"crosshair": Crosshair,
|
|
189
|
+
"database": Database,
|
|
190
|
+
"dollar-sign": DollarSign,
|
|
191
|
+
"download": Download,
|
|
192
|
+
"droplet": Droplet,
|
|
193
|
+
"ellipsis-vertical": EllipsisVertical,
|
|
194
|
+
"external-link": ExternalLink,
|
|
195
|
+
"eye": Eye,
|
|
196
|
+
"eye-off": EyeOff,
|
|
197
|
+
"face-slightly-smiling": FaceSlightlySmiling,
|
|
198
|
+
"file": File,
|
|
199
|
+
"file-text": FileText,
|
|
200
|
+
"folder": Folder,
|
|
201
|
+
"gift": Gift,
|
|
202
|
+
"git-branch": GitBranch,
|
|
203
|
+
"git-pull-request": GitPullRequest,
|
|
204
|
+
"globe": Globe,
|
|
205
|
+
"grid-3x3": Grid3x3,
|
|
206
|
+
"hash": Hash,
|
|
207
|
+
"heart": Heart,
|
|
208
|
+
"hexagon": Hexagon,
|
|
209
|
+
"house": House,
|
|
210
|
+
"image": Image,
|
|
211
|
+
"inbox": Inbox,
|
|
212
|
+
"info": Info,
|
|
213
|
+
"italic": Italic,
|
|
214
|
+
"key": Key,
|
|
215
|
+
"layers": Layers,
|
|
216
|
+
"layout-grid": LayoutGrid,
|
|
217
|
+
"link": Link,
|
|
218
|
+
"link-2": Link2,
|
|
219
|
+
"list": List,
|
|
220
|
+
"loader": Loader,
|
|
221
|
+
"lock": Lock,
|
|
222
|
+
"lock-open": LockOpen,
|
|
223
|
+
"log-in": LogIn,
|
|
224
|
+
"log-out": LogOut,
|
|
225
|
+
"mail": Mail,
|
|
226
|
+
"map": Map,
|
|
227
|
+
"map-pin": MapPin,
|
|
228
|
+
"maximize": Maximize,
|
|
229
|
+
"maximize-2": Maximize2,
|
|
230
|
+
"menu": Menu,
|
|
231
|
+
"message-circle": MessageCircle,
|
|
232
|
+
"message-square": MessageSquare,
|
|
233
|
+
"mic": Mic,
|
|
234
|
+
"mic-off": MicOff,
|
|
235
|
+
"minus": Minus,
|
|
236
|
+
"monitor": Monitor,
|
|
237
|
+
"moon": Moon,
|
|
238
|
+
"music": Music,
|
|
239
|
+
"navigation": Navigation,
|
|
240
|
+
"package": Package,
|
|
241
|
+
"panel-left": PanelLeft,
|
|
242
|
+
"panels-top-left": PanelsTopLeft,
|
|
243
|
+
"party-popper": PartyPopper,
|
|
244
|
+
"pen": Pen,
|
|
245
|
+
"pen-line": PenLine,
|
|
246
|
+
"pencil": Pencil,
|
|
247
|
+
"percent": Percent,
|
|
248
|
+
"phone": Phone,
|
|
249
|
+
"play": Play,
|
|
250
|
+
"plus": Plus,
|
|
251
|
+
"printer": Printer,
|
|
252
|
+
"radio": Radio,
|
|
253
|
+
"refresh-cw": RefreshCw,
|
|
254
|
+
"repeat": Repeat,
|
|
255
|
+
"rotate-cw": RotateCw,
|
|
256
|
+
"search": Search,
|
|
257
|
+
"send": Send,
|
|
258
|
+
"server": Server,
|
|
259
|
+
"settings": Settings,
|
|
260
|
+
"share": Share,
|
|
261
|
+
"share-2": Share2,
|
|
262
|
+
"shield": Shield,
|
|
263
|
+
"shield-off": ShieldOff,
|
|
264
|
+
"shopping-bag": ShoppingBag,
|
|
265
|
+
"shopping-cart": ShoppingCart,
|
|
266
|
+
"shuffle": Shuffle,
|
|
267
|
+
"slash": Slash,
|
|
268
|
+
"sliders-horizontal": SlidersHorizontal,
|
|
269
|
+
"sliders-vertical": SlidersVertical,
|
|
270
|
+
"smartphone": Smartphone,
|
|
271
|
+
"square": Square,
|
|
272
|
+
"square-check-big": SquareCheckBig,
|
|
273
|
+
"square-pen": SquarePen,
|
|
274
|
+
"star": Star,
|
|
275
|
+
"sun": Sun,
|
|
276
|
+
"tag": Tag,
|
|
277
|
+
"terminal": Terminal,
|
|
278
|
+
"thumbs-up": ThumbsUp,
|
|
279
|
+
"trash": Trash,
|
|
280
|
+
"trending-down": TrendingDown,
|
|
281
|
+
"trending-up": TrendingUp,
|
|
282
|
+
"triangle-alert": TriangleAlert,
|
|
283
|
+
"truck": Truck,
|
|
284
|
+
"underline": Underline,
|
|
285
|
+
"upload": Upload,
|
|
286
|
+
"user": User,
|
|
287
|
+
"user-check": UserCheck,
|
|
288
|
+
"user-minus": UserMinus,
|
|
289
|
+
"user-plus": UserPlus,
|
|
290
|
+
"users": Users,
|
|
291
|
+
"video": Video,
|
|
292
|
+
"wifi-off": WifiOff,
|
|
293
|
+
"wrench": Wrench,
|
|
294
|
+
"x": X,
|
|
295
|
+
"zap": Zap,
|
|
296
|
+
};
|
|
@@ -1,34 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tap-away keyboard dismissal that never steals a touch.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Let the deepest interactive view win the responder negotiation, and blur on
|
|
5
|
+
* release only for an unclaimed, single-finger tap within the travel slop.
|
|
6
|
+
* Hosted `@expo/ui` inputs register with RN's `TextInputState`. A ScrollView
|
|
7
|
+
* using `keyboardShouldPersistTaps="handled"` can therefore claim dead space
|
|
8
|
+
* and blur on release if it observed no scroll, even after substantial travel.
|
|
9
|
+
* `DismissKeyboard`'s ScrollView uses `always` so this boundary owns tap dismissal
|
|
10
|
+
* with an explicit travel policy, independent of whether content can scroll.
|
|
11
|
+
* Two signals need no rectangles and no responder ownership:
|
|
10
12
|
*
|
|
11
13
|
* 1. `onStartShouldSetResponder` in the *bubble* phase is only ever called on a
|
|
12
|
-
* view when no descendant claimed the touch. Pressables
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* view when no descendant claimed the touch. Pressables and RN TextInputs
|
|
15
|
+
* claim, as can scroll views depending on their tap policy. Being asked
|
|
16
|
+
* means no descendant took responder ownership, not necessarily dead space.
|
|
15
17
|
* 2. The package `TextInput` tags touches that begin on its surface
|
|
16
18
|
* (`markTextInputTouchStart`). Its bubble handler runs before any enclosing
|
|
17
19
|
* boundary's, so the boundary can tell a tap on *any* native field — focused
|
|
18
20
|
* or not — from dead space, and leave focus handoff, double-tap selection
|
|
19
21
|
* and the eye toggle to the platform.
|
|
20
22
|
*
|
|
21
|
-
* The boundary always answers `false`, so
|
|
22
|
-
* controls (SwiftUI/Compose fields, sliders) keep receiving every touch phase
|
|
23
|
+
* The boundary always answers `false`, so it never becomes the JS responder.
|
|
24
|
+
* Native controls (SwiftUI/Compose fields, sliders) keep receiving every touch phase
|
|
23
25
|
* on both platforms, and scroll views take over drags exactly as before.
|
|
24
26
|
* Dismissal happens in `onTouchEnd`, a plain bubbling event that fires whether
|
|
25
|
-
* or not anyone became the responder
|
|
27
|
+
* or not anyone became the responder. Moves cancel permanently once past the
|
|
28
|
+
* slop; release coordinates also enforce it when move delivery was missed.
|
|
29
|
+
* Cancellation and multitouch drop the tap without rearming another finger.
|
|
26
30
|
*/
|
|
27
31
|
import { useMemo, useRef } from "react";
|
|
28
32
|
import { Platform } from "react-native";
|
|
29
33
|
import { KeyboardController, useKeyboardState } from "./keyboardController";
|
|
30
34
|
import { dismissKeyboardFocusedInput, hasKeyboardFocusedInput } from "./keyboardFocusRegistry.js";
|
|
31
|
-
/**
|
|
35
|
+
/** Maximum displacement per axis (RN logical units) for a tap, scrollable or not. */
|
|
32
36
|
const MOVE_SLOP = 10;
|
|
33
37
|
function keyOf(event) {
|
|
34
38
|
const { identifier, timestamp } = event.nativeEvent;
|
|
@@ -42,6 +46,10 @@ function isSameEvent(key, event) {
|
|
|
42
46
|
}
|
|
43
47
|
let textInputTouch = null;
|
|
44
48
|
let claimedTouch = null;
|
|
49
|
+
// An ancestor can observe a second finger outside an inner boundary/surface.
|
|
50
|
+
// Invalidate already-armed taps there too, even if that finger lifts first and
|
|
51
|
+
// the inner view only receives its original finger's final touches=[] event.
|
|
52
|
+
let multiTouchVersion = 0;
|
|
45
53
|
/**
|
|
46
54
|
* `onStartShouldSetResponder` for a native text-field surface: decline the
|
|
47
55
|
* responder (the native field keeps every touch phase) but tag the touch so an
|
|
@@ -72,6 +80,77 @@ function movedPastSlop(tap, event) {
|
|
|
72
80
|
return (Math.abs(event.nativeEvent.pageX - tap.pageX) > MOVE_SLOP ||
|
|
73
81
|
Math.abs(event.nativeEvent.pageY - tap.pageY) > MOVE_SLOP);
|
|
74
82
|
}
|
|
83
|
+
/** Shared single-finger travel policy for tap-away dismissal and surface focus. */
|
|
84
|
+
function createTapTracker() {
|
|
85
|
+
let pending = null;
|
|
86
|
+
let armedVersion = multiTouchVersion;
|
|
87
|
+
let lastStart = null;
|
|
88
|
+
const active = new Set();
|
|
89
|
+
function readTouches(event) {
|
|
90
|
+
const { touches } = event.nativeEvent;
|
|
91
|
+
if (touches == null)
|
|
92
|
+
return false;
|
|
93
|
+
active.clear();
|
|
94
|
+
touches.forEach(({ identifier }) => active.add(identifier));
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
start(event) {
|
|
99
|
+
// Negotiation precedes the plain bubbling touch-start for the same event.
|
|
100
|
+
// Observe it once; a child-claimed start reaches only onTouchStart, which
|
|
101
|
+
// clears any stale pending tap but never arms one.
|
|
102
|
+
if (isSameEvent(lastStart, event))
|
|
103
|
+
return false;
|
|
104
|
+
lastStart = keyOf(event);
|
|
105
|
+
pending = null;
|
|
106
|
+
// RN's array is authoritative, including after a missed end. The set is
|
|
107
|
+
// also a fallback for callers/tests omitting arrays, retaining other down
|
|
108
|
+
// fingers even after movement has cancelled the pending tap.
|
|
109
|
+
if (!readTouches(event))
|
|
110
|
+
active.add(event.nativeEvent.identifier);
|
|
111
|
+
if (active.size > 1 || (event.nativeEvent.changedTouches?.length ?? 1) > 1) {
|
|
112
|
+
multiTouchVersion += 1;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return active.size === 1;
|
|
116
|
+
},
|
|
117
|
+
arm(event) {
|
|
118
|
+
pending = { ...keyOf(event), pageX: event.nativeEvent.pageX, pageY: event.nativeEvent.pageY };
|
|
119
|
+
armedVersion = multiTouchVersion;
|
|
120
|
+
},
|
|
121
|
+
move(event) {
|
|
122
|
+
readTouches(event);
|
|
123
|
+
if (active.size > 1 || (event.nativeEvent.changedTouches?.length ?? 1) > 1) {
|
|
124
|
+
multiTouchVersion += 1;
|
|
125
|
+
pending = null;
|
|
126
|
+
}
|
|
127
|
+
else if (isSameTouch(pending, event) && movedPastSlop(pending, event)) {
|
|
128
|
+
pending = null;
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
end(event) {
|
|
132
|
+
const tap = pending;
|
|
133
|
+
pending = null;
|
|
134
|
+
// On touch end, touches contains the REMAINING fingers (empty on the last
|
|
135
|
+
// release); changedTouches contains those that ended. Never treat the
|
|
136
|
+
// final empty array as an invalid single-finger tap.
|
|
137
|
+
if (!readTouches(event)) {
|
|
138
|
+
active.delete(event.nativeEvent.identifier);
|
|
139
|
+
event.nativeEvent.changedTouches?.forEach(({ identifier }) => active.delete(identifier));
|
|
140
|
+
}
|
|
141
|
+
if (active.size === 0)
|
|
142
|
+
lastStart = null;
|
|
143
|
+
if (active.size > 0 || (event.nativeEvent.changedTouches?.length ?? 1) > 1) {
|
|
144
|
+
multiTouchVersion += 1;
|
|
145
|
+
}
|
|
146
|
+
return (armedVersion === multiTouchVersion &&
|
|
147
|
+
active.size === 0 &&
|
|
148
|
+
(event.nativeEvent.changedTouches?.length ?? 1) <= 1 &&
|
|
149
|
+
isSameTouch(tap, event) &&
|
|
150
|
+
!movedPastSlop(tap, event));
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
75
154
|
/**
|
|
76
155
|
* View props for the painted surface around a native (`@expo/ui`) text field.
|
|
77
156
|
*
|
|
@@ -87,31 +166,30 @@ function movedPastSlop(tap, event) {
|
|
|
87
166
|
export function useTextInputSurfaceResponder(focus) {
|
|
88
167
|
const focusRef = useRef(focus);
|
|
89
168
|
focusRef.current = focus;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
tap.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}), []);
|
|
169
|
+
return useMemo(() => {
|
|
170
|
+
const tap = createTapTracker();
|
|
171
|
+
return {
|
|
172
|
+
onStartShouldSetResponder: (event) => {
|
|
173
|
+
markTextInputTouchStart(event);
|
|
174
|
+
if (tap.start(event))
|
|
175
|
+
tap.arm(event);
|
|
176
|
+
return false;
|
|
177
|
+
},
|
|
178
|
+
onTouchStart: (event) => {
|
|
179
|
+
tap.start(event);
|
|
180
|
+
},
|
|
181
|
+
onTouchMove: (event) => {
|
|
182
|
+
tap.move(event);
|
|
183
|
+
},
|
|
184
|
+
onTouchEnd: (event) => {
|
|
185
|
+
if (tap.end(event))
|
|
186
|
+
focusRef.current();
|
|
187
|
+
},
|
|
188
|
+
onTouchCancel: (event) => {
|
|
189
|
+
tap.end(event);
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}, []);
|
|
115
193
|
}
|
|
116
194
|
/**
|
|
117
195
|
* View props for a tap-away keyboard-dismiss boundary. Spread onto the wrapper
|
|
@@ -123,13 +201,14 @@ export function useKeyboardDismissResponder() {
|
|
|
123
201
|
const isVisible = useKeyboardState((state) => state.isVisible);
|
|
124
202
|
const isVisibleRef = useRef(isVisible);
|
|
125
203
|
isVisibleRef.current = isVisible;
|
|
126
|
-
const pending = useRef(null);
|
|
127
204
|
return useMemo(() => {
|
|
128
205
|
if (Platform.OS === "web")
|
|
129
206
|
return {};
|
|
207
|
+
const tap = createTapTracker();
|
|
130
208
|
return {
|
|
131
209
|
onStartShouldSetResponder: (event) => {
|
|
132
|
-
|
|
210
|
+
if (!tap.start(event))
|
|
211
|
+
return false;
|
|
133
212
|
// `hasKeyboardFocusedInput` covers the window where a field has focus but
|
|
134
213
|
// keyboard-controller has not reported the keyboard yet (or a hardware
|
|
135
214
|
// keyboard is attached and there is no software keyboard to observe).
|
|
@@ -141,29 +220,21 @@ export function useKeyboardDismissResponder() {
|
|
|
141
220
|
if (isSameEvent(claimedTouch, event))
|
|
142
221
|
return false;
|
|
143
222
|
claimedTouch = keyOf(event);
|
|
144
|
-
|
|
223
|
+
tap.arm(event);
|
|
145
224
|
return false;
|
|
146
225
|
},
|
|
147
226
|
onTouchStart: (event) => {
|
|
148
|
-
|
|
149
|
-
// event, so a fresh pending tap matches here; anything else is stale
|
|
150
|
-
// (e.g. a touch whose end never reached this view).
|
|
151
|
-
if (pending.current && !isSameEvent(pending.current, event))
|
|
152
|
-
pending.current = null;
|
|
227
|
+
tap.start(event);
|
|
153
228
|
},
|
|
154
229
|
onTouchMove: (event) => {
|
|
155
|
-
|
|
156
|
-
pending.current = null;
|
|
157
|
-
}
|
|
230
|
+
tap.move(event);
|
|
158
231
|
},
|
|
159
232
|
onTouchEnd: (event) => {
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
pending.current = null;
|
|
163
|
-
dismissKeyboard();
|
|
233
|
+
if (tap.end(event))
|
|
234
|
+
dismissKeyboard();
|
|
164
235
|
},
|
|
165
|
-
onTouchCancel: () => {
|
|
166
|
-
|
|
236
|
+
onTouchCancel: (event) => {
|
|
237
|
+
tap.end(event);
|
|
167
238
|
},
|
|
168
239
|
};
|
|
169
240
|
}, []);
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Registry of the currently focused native (`@expo/ui`) text field.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Hosted `@expo/ui` inputs also register with React Native's `TextInputState`.
|
|
5
|
+
* This package registry supplies a window-independent native blur handle and
|
|
6
|
+
* focus presence even when keyboard-controller cannot observe the keyboard
|
|
7
|
+
* (for example, in an isolated native sheet window). The package `TextInput`
|
|
8
|
+
* registers on focus; tap-away dismissal (`keyboardDismiss.ts`, mounted by
|
|
9
|
+
* `DismissKeyboard` and `BottomSheet.Content`) resigns the field through that
|
|
10
|
+
* handle.
|
|
8
11
|
*/
|
|
9
12
|
export type KeyboardFocusedInputToken = object;
|
|
10
13
|
/**
|
|
@@ -21,5 +24,12 @@ export declare function hasKeyboardFocusedInput(): boolean;
|
|
|
21
24
|
/**
|
|
22
25
|
* Resign the currently-focused native field, dismissing its keyboard. Returns
|
|
23
26
|
* `true` if a field was focused and a blur handle was available.
|
|
27
|
+
*
|
|
28
|
+
* Presence is cleared here, in the same call, rather than left to the field's
|
|
29
|
+
* later `onBlur`: consumers call `dismissKeyboard()` from submit handlers and
|
|
30
|
+
* the native blur callback is not guaranteed to arrive (isolated sheet window,
|
|
31
|
+
* iOS secure/plain handoff), which used to leave a stale registration behind.
|
|
32
|
+
* The clear is token-guarded, so a field that takes focus synchronously during
|
|
33
|
+
* `blur()` keeps its registration; the field's own later clear is idempotent.
|
|
24
34
|
*/
|
|
25
35
|
export declare function dismissKeyboardFocusedInput(): boolean;
|
|
@@ -38,11 +38,19 @@ export function hasKeyboardFocusedInput() {
|
|
|
38
38
|
/**
|
|
39
39
|
* Resign the currently-focused native field, dismissing its keyboard. Returns
|
|
40
40
|
* `true` if a field was focused and a blur handle was available.
|
|
41
|
+
*
|
|
42
|
+
* Presence is cleared here, in the same call, rather than left to the field's
|
|
43
|
+
* later `onBlur`: consumers call `dismissKeyboard()` from submit handlers and
|
|
44
|
+
* the native blur callback is not guaranteed to arrive (isolated sheet window,
|
|
45
|
+
* iOS secure/plain handoff), which used to leave a stale registration behind.
|
|
46
|
+
* The clear is token-guarded, so a field that takes focus synchronously during
|
|
47
|
+
* `blur()` keeps its registration; the field's own later clear is idempotent.
|
|
41
48
|
*/
|
|
42
49
|
export function dismissKeyboardFocusedInput() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
const entry = focusedInput;
|
|
51
|
+
if (!entry)
|
|
52
|
+
return false;
|
|
53
|
+
entry.blur();
|
|
54
|
+
clearKeyboardFocusedInput(entry.token);
|
|
55
|
+
return true;
|
|
48
56
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { TextInputProps } from "@expo/ui";
|
|
2
|
+
export type NativeTextFieldProps = TextInputProps;
|
|
3
|
+
export declare function NativeTextField({ ref, value, onChangeText, placeholder, autoFocus, editable: editableProp, multiline, keyboardType: keyboardTypeProp, autoCapitalize, autoCorrect, returnKeyType: returnKeyTypeProp, onSubmitEditing, onFocus, onBlur, cursorColor, textAlign, readOnly, inputMode, enterKeyHint, defaultValue, numberOfLines: numberOfLinesProp, rows, testID, placeholderTextColor, textStyle, style, secureTextEntry, autoComplete, onContentSizeChange, maxLength, caretHidden, selectionColor, selectionHandleColor, selection, onSelectionChange, selectTextOnFocus, modifiers: userModifiers, }: NativeTextFieldProps): import("react").JSX.Element;
|