@multiplatform.one/forms 7.7.6 → 7.10.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 (497) hide show
  1. package/dist/cjs/Button/index.cjs +1 -4
  2. package/dist/cjs/Button/index.native.js +5 -6
  3. package/dist/cjs/Button/index.native.js.map +1 -1
  4. package/dist/cjs/Chip/index.cjs +1 -1
  5. package/dist/cjs/Chip/index.native.js +1 -1
  6. package/dist/cjs/Chip/index.native.js.map +1 -1
  7. package/dist/cjs/FloatingPanel/index.cjs +59 -14
  8. package/dist/cjs/FloatingPanel/index.native.js +16 -7
  9. package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
  10. package/dist/cjs/FloatingPanel/nestedAction.cjs +37 -0
  11. package/dist/cjs/FloatingPanel/nestedAction.native.js +42 -0
  12. package/dist/cjs/FloatingPanel/nestedAction.native.js.map +1 -0
  13. package/dist/cjs/InputParts/index.cjs +19 -8
  14. package/dist/cjs/InputParts/index.native.js +20 -11
  15. package/dist/cjs/InputParts/index.native.js.map +1 -1
  16. package/dist/cjs/Wheel/index.cjs +3 -3
  17. package/dist/cjs/Wheel/index.native.js +3 -3
  18. package/dist/cjs/Wheel/index.native.js.map +1 -1
  19. package/dist/cjs/fieldLayout.cjs +3 -3
  20. package/dist/cjs/fieldLayout.native.js +3 -3
  21. package/dist/cjs/fieldLayout.native.js.map +1 -1
  22. package/dist/cjs/fields/Checkbox/CheckboxFieldFrame.cjs +9 -2
  23. package/dist/cjs/fields/Checkbox/CheckboxFieldFrame.native.js +10 -1
  24. package/dist/cjs/fields/Checkbox/CheckboxFieldFrame.native.js.map +1 -1
  25. package/dist/cjs/fields/Combobox/index.cjs +36 -29
  26. package/dist/cjs/fields/Combobox/index.native.js +38 -38
  27. package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
  28. package/dist/cjs/fields/DatePicker/Calendar.cjs +6 -1
  29. package/dist/cjs/fields/DatePicker/Calendar.native.js +6 -1
  30. package/dist/cjs/fields/DatePicker/Calendar.native.js.map +1 -1
  31. package/dist/cjs/fields/DatePicker/DateRangePicker.cjs +2 -2
  32. package/dist/cjs/fields/DatePicker/DateRangePicker.native.js +2 -2
  33. package/dist/cjs/fields/DatePicker/DateRangePicker.native.js.map +1 -1
  34. package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.cjs +15 -12
  35. package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.native.js +16 -12
  36. package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
  37. package/dist/cjs/fields/DatePicker/DatetimePicker.native.js +39 -14
  38. package/dist/cjs/fields/DatePicker/DatetimePicker.native.js.map +1 -1
  39. package/dist/cjs/fields/DatePicker/MonthPicker.cjs +10 -3
  40. package/dist/cjs/fields/DatePicker/MonthPicker.native.js +10 -3
  41. package/dist/cjs/fields/DatePicker/MonthPicker.native.js.map +1 -1
  42. package/dist/cjs/fields/DatePicker/MultiDatePicker.cjs +2 -2
  43. package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js +2 -2
  44. package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
  45. package/dist/cjs/fields/DatePicker/index.native.js +3 -0
  46. package/dist/cjs/fields/DatePicker/index.native.js.map +1 -1
  47. package/dist/cjs/fields/DatePicker/parts.cjs +26 -14
  48. package/dist/cjs/fields/DatePicker/parts.native.js +28 -14
  49. package/dist/cjs/fields/DatePicker/parts.native.js.map +1 -1
  50. package/dist/cjs/fields/FileUpload/index.cjs +6 -0
  51. package/dist/cjs/fields/FileUpload/index.native.js +6 -0
  52. package/dist/cjs/fields/FileUpload/index.native.js.map +1 -1
  53. package/dist/cjs/fields/Geolocation/GeolocationMap.native.js.map +1 -1
  54. package/dist/cjs/fields/Geolocation/geocoder.cjs +63 -0
  55. package/dist/cjs/fields/Geolocation/geocoder.native.js +68 -0
  56. package/dist/cjs/fields/Geolocation/geocoder.native.js.map +1 -0
  57. package/dist/cjs/fields/Geolocation/index.cjs +801 -152
  58. package/dist/cjs/fields/Geolocation/index.native.js +856 -167
  59. package/dist/cjs/fields/Geolocation/index.native.js.map +1 -1
  60. package/dist/cjs/fields/Geolocation/nativeSearchFocus.cjs +44 -0
  61. package/dist/cjs/fields/Geolocation/nativeSearchFocus.native.js +47 -0
  62. package/dist/cjs/fields/Geolocation/nativeSearchFocus.native.js.map +1 -0
  63. package/dist/cjs/fields/Icon/drawnSet.cjs +16 -3
  64. package/dist/cjs/fields/Icon/drawnSet.native.js +20 -3
  65. package/dist/cjs/fields/Icon/drawnSet.native.js.map +1 -1
  66. package/dist/cjs/fields/Icon/index.cjs +102 -27
  67. package/dist/cjs/fields/Icon/index.native.js +115 -31
  68. package/dist/cjs/fields/Icon/index.native.js.map +1 -1
  69. package/dist/cjs/fields/Input/index.cjs +5 -12
  70. package/dist/cjs/fields/Input/index.native.js +5 -13
  71. package/dist/cjs/fields/Input/index.native.js.map +1 -1
  72. package/dist/cjs/fields/PhoneInput/index.cjs +2 -2
  73. package/dist/cjs/fields/PhoneInput/index.native.js +2 -2
  74. package/dist/cjs/fields/PhoneInput/index.native.js.map +1 -1
  75. package/dist/cjs/fields/RadioGroup/index.cjs +34 -5
  76. package/dist/cjs/fields/RadioGroup/index.native.js +49 -7
  77. package/dist/cjs/fields/RadioGroup/index.native.js.map +1 -1
  78. package/dist/cjs/fields/SearchInput/index.cjs +8 -9
  79. package/dist/cjs/fields/SearchInput/index.native.js +8 -9
  80. package/dist/cjs/fields/SearchInput/index.native.js.map +1 -1
  81. package/dist/cjs/fields/Select/index.cjs +42 -11
  82. package/dist/cjs/fields/Select/index.native.js +52 -20
  83. package/dist/cjs/fields/Select/index.native.js.map +1 -1
  84. package/dist/cjs/fields/Stepper/index.cjs +144 -31
  85. package/dist/cjs/fields/Stepper/index.native.js +151 -31
  86. package/dist/cjs/fields/Stepper/index.native.js.map +1 -1
  87. package/dist/cjs/fields/ToggleGroup/index.cjs +35 -2
  88. package/dist/cjs/fields/ToggleGroup/index.native.js +42 -2
  89. package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
  90. package/dist/cjs/index.cjs +2 -0
  91. package/dist/cjs/index.native.js +2 -0
  92. package/dist/cjs/index.native.js.map +1 -1
  93. package/dist/cjs/shared/focusManagement.cjs +24 -11
  94. package/dist/cjs/shared/focusManagement.native.js +24 -11
  95. package/dist/cjs/shared/focusManagement.native.js.map +1 -1
  96. package/dist/cjs/shared/nativeA11y.cjs +57 -0
  97. package/dist/cjs/shared/nativeA11y.native.js +61 -0
  98. package/dist/cjs/shared/nativeA11y.native.js.map +1 -0
  99. package/dist/esm/Button/index.mjs +1 -4
  100. package/dist/esm/Button/index.mjs.map +1 -1
  101. package/dist/esm/Button/index.native.js +5 -6
  102. package/dist/esm/Button/index.native.js.map +1 -1
  103. package/dist/esm/Chip/index.mjs +1 -1
  104. package/dist/esm/Chip/index.mjs.map +1 -1
  105. package/dist/esm/Chip/index.native.js +1 -1
  106. package/dist/esm/Chip/index.native.js.map +1 -1
  107. package/dist/esm/FloatingPanel/index.mjs +58 -15
  108. package/dist/esm/FloatingPanel/index.mjs.map +1 -1
  109. package/dist/esm/FloatingPanel/index.native.js +15 -8
  110. package/dist/esm/FloatingPanel/index.native.js.map +1 -1
  111. package/dist/esm/FloatingPanel/nestedAction.mjs +11 -0
  112. package/dist/esm/FloatingPanel/nestedAction.mjs.map +1 -0
  113. package/dist/esm/FloatingPanel/nestedAction.native.js +13 -0
  114. package/dist/esm/FloatingPanel/nestedAction.native.js.map +1 -0
  115. package/dist/esm/InputParts/index.mjs +20 -9
  116. package/dist/esm/InputParts/index.mjs.map +1 -1
  117. package/dist/esm/InputParts/index.native.js +21 -12
  118. package/dist/esm/InputParts/index.native.js.map +1 -1
  119. package/dist/esm/Wheel/index.mjs +3 -3
  120. package/dist/esm/Wheel/index.mjs.map +1 -1
  121. package/dist/esm/Wheel/index.native.js +3 -3
  122. package/dist/esm/Wheel/index.native.js.map +1 -1
  123. package/dist/esm/fieldLayout.mjs +3 -3
  124. package/dist/esm/fieldLayout.mjs.map +1 -1
  125. package/dist/esm/fieldLayout.native.js +3 -3
  126. package/dist/esm/fieldLayout.native.js.map +1 -1
  127. package/dist/esm/fields/Checkbox/CheckboxFieldFrame.mjs +8 -1
  128. package/dist/esm/fields/Checkbox/CheckboxFieldFrame.mjs.map +1 -1
  129. package/dist/esm/fields/Checkbox/CheckboxFieldFrame.native.js +10 -1
  130. package/dist/esm/fields/Checkbox/CheckboxFieldFrame.native.js.map +1 -1
  131. package/dist/esm/fields/Combobox/index.mjs +36 -29
  132. package/dist/esm/fields/Combobox/index.mjs.map +1 -1
  133. package/dist/esm/fields/Combobox/index.native.js +38 -38
  134. package/dist/esm/fields/Combobox/index.native.js.map +1 -1
  135. package/dist/esm/fields/DatePicker/Calendar.mjs +6 -1
  136. package/dist/esm/fields/DatePicker/Calendar.mjs.map +1 -1
  137. package/dist/esm/fields/DatePicker/Calendar.native.js +6 -1
  138. package/dist/esm/fields/DatePicker/Calendar.native.js.map +1 -1
  139. package/dist/esm/fields/DatePicker/DateRangePicker.mjs +2 -2
  140. package/dist/esm/fields/DatePicker/DateRangePicker.mjs.map +1 -1
  141. package/dist/esm/fields/DatePicker/DateRangePicker.native.js +2 -2
  142. package/dist/esm/fields/DatePicker/DateRangePicker.native.js.map +1 -1
  143. package/dist/esm/fields/DatePicker/DatetimePicker.catalog.mjs +15 -12
  144. package/dist/esm/fields/DatePicker/DatetimePicker.catalog.mjs.map +1 -1
  145. package/dist/esm/fields/DatePicker/DatetimePicker.catalog.native.js +16 -12
  146. package/dist/esm/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
  147. package/dist/esm/fields/DatePicker/DatetimePicker.native.js +39 -14
  148. package/dist/esm/fields/DatePicker/DatetimePicker.native.js.map +1 -1
  149. package/dist/esm/fields/DatePicker/MonthPicker.mjs +10 -3
  150. package/dist/esm/fields/DatePicker/MonthPicker.mjs.map +1 -1
  151. package/dist/esm/fields/DatePicker/MonthPicker.native.js +10 -3
  152. package/dist/esm/fields/DatePicker/MonthPicker.native.js.map +1 -1
  153. package/dist/esm/fields/DatePicker/MultiDatePicker.mjs +2 -2
  154. package/dist/esm/fields/DatePicker/MultiDatePicker.mjs.map +1 -1
  155. package/dist/esm/fields/DatePicker/MultiDatePicker.native.js +2 -2
  156. package/dist/esm/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
  157. package/dist/esm/fields/DatePicker/index.native.js +3 -0
  158. package/dist/esm/fields/DatePicker/index.native.js.map +1 -1
  159. package/dist/esm/fields/DatePicker/parts.mjs +28 -16
  160. package/dist/esm/fields/DatePicker/parts.mjs.map +1 -1
  161. package/dist/esm/fields/DatePicker/parts.native.js +30 -16
  162. package/dist/esm/fields/DatePicker/parts.native.js.map +1 -1
  163. package/dist/esm/fields/FileUpload/index.mjs +6 -0
  164. package/dist/esm/fields/FileUpload/index.mjs.map +1 -1
  165. package/dist/esm/fields/FileUpload/index.native.js +6 -0
  166. package/dist/esm/fields/FileUpload/index.native.js.map +1 -1
  167. package/dist/esm/fields/Geolocation/GeolocationMap.mjs.map +1 -1
  168. package/dist/esm/fields/Geolocation/GeolocationMap.native.js.map +1 -1
  169. package/dist/esm/fields/Geolocation/geocoder.mjs +33 -0
  170. package/dist/esm/fields/Geolocation/geocoder.mjs.map +1 -0
  171. package/dist/esm/fields/Geolocation/geocoder.native.js +35 -0
  172. package/dist/esm/fields/Geolocation/geocoder.native.js.map +1 -0
  173. package/dist/esm/fields/Geolocation/index.mjs +792 -149
  174. package/dist/esm/fields/Geolocation/index.mjs.map +1 -1
  175. package/dist/esm/fields/Geolocation/index.native.js +847 -164
  176. package/dist/esm/fields/Geolocation/index.native.js.map +1 -1
  177. package/dist/esm/fields/Geolocation/nativeSearchFocus.mjs +19 -0
  178. package/dist/esm/fields/Geolocation/nativeSearchFocus.mjs.map +1 -0
  179. package/dist/esm/fields/Geolocation/nativeSearchFocus.native.js +19 -0
  180. package/dist/esm/fields/Geolocation/nativeSearchFocus.native.js.map +1 -0
  181. package/dist/esm/fields/Icon/drawnSet.mjs +16 -3
  182. package/dist/esm/fields/Icon/drawnSet.mjs.map +1 -1
  183. package/dist/esm/fields/Icon/drawnSet.native.js +20 -3
  184. package/dist/esm/fields/Icon/drawnSet.native.js.map +1 -1
  185. package/dist/esm/fields/Icon/index.mjs +93 -18
  186. package/dist/esm/fields/Icon/index.mjs.map +1 -1
  187. package/dist/esm/fields/Icon/index.native.js +106 -22
  188. package/dist/esm/fields/Icon/index.native.js.map +1 -1
  189. package/dist/esm/fields/Input/index.mjs +5 -12
  190. package/dist/esm/fields/Input/index.mjs.map +1 -1
  191. package/dist/esm/fields/Input/index.native.js +5 -13
  192. package/dist/esm/fields/Input/index.native.js.map +1 -1
  193. package/dist/esm/fields/PhoneInput/index.mjs +2 -2
  194. package/dist/esm/fields/PhoneInput/index.mjs.map +1 -1
  195. package/dist/esm/fields/PhoneInput/index.native.js +2 -2
  196. package/dist/esm/fields/PhoneInput/index.native.js.map +1 -1
  197. package/dist/esm/fields/RadioGroup/index.mjs +34 -5
  198. package/dist/esm/fields/RadioGroup/index.mjs.map +1 -1
  199. package/dist/esm/fields/RadioGroup/index.native.js +49 -7
  200. package/dist/esm/fields/RadioGroup/index.native.js.map +1 -1
  201. package/dist/esm/fields/SearchInput/index.mjs +9 -10
  202. package/dist/esm/fields/SearchInput/index.mjs.map +1 -1
  203. package/dist/esm/fields/SearchInput/index.native.js +9 -10
  204. package/dist/esm/fields/SearchInput/index.native.js.map +1 -1
  205. package/dist/esm/fields/Select/index.mjs +42 -11
  206. package/dist/esm/fields/Select/index.mjs.map +1 -1
  207. package/dist/esm/fields/Select/index.native.js +52 -20
  208. package/dist/esm/fields/Select/index.native.js.map +1 -1
  209. package/dist/esm/fields/Stepper/index.mjs +145 -32
  210. package/dist/esm/fields/Stepper/index.mjs.map +1 -1
  211. package/dist/esm/fields/Stepper/index.native.js +152 -32
  212. package/dist/esm/fields/Stepper/index.native.js.map +1 -1
  213. package/dist/esm/fields/ToggleGroup/index.mjs +35 -2
  214. package/dist/esm/fields/ToggleGroup/index.mjs.map +1 -1
  215. package/dist/esm/fields/ToggleGroup/index.native.js +42 -2
  216. package/dist/esm/fields/ToggleGroup/index.native.js.map +1 -1
  217. package/dist/esm/index.mjs +2 -1
  218. package/dist/esm/index.mjs.map +1 -1
  219. package/dist/esm/index.native.js +2 -1
  220. package/dist/esm/index.native.js.map +1 -1
  221. package/dist/esm/shared/focusManagement.mjs +24 -11
  222. package/dist/esm/shared/focusManagement.mjs.map +1 -1
  223. package/dist/esm/shared/focusManagement.native.js +24 -11
  224. package/dist/esm/shared/focusManagement.native.js.map +1 -1
  225. package/dist/esm/shared/nativeA11y.mjs +31 -0
  226. package/dist/esm/shared/nativeA11y.mjs.map +1 -0
  227. package/dist/esm/shared/nativeA11y.native.js +32 -0
  228. package/dist/esm/shared/nativeA11y.native.js.map +1 -0
  229. package/dist/jsx/Button/index.mjs +1 -4
  230. package/dist/jsx/Button/index.mjs.map +1 -1
  231. package/dist/jsx/Button/index.native.js +5 -6
  232. package/dist/jsx/Button/index.native.js.map +1 -1
  233. package/dist/jsx/Chip/index.mjs +1 -1
  234. package/dist/jsx/Chip/index.mjs.map +1 -1
  235. package/dist/jsx/Chip/index.native.js +1 -1
  236. package/dist/jsx/Chip/index.native.js.map +1 -1
  237. package/dist/jsx/FloatingPanel/index.mjs +58 -15
  238. package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
  239. package/dist/jsx/FloatingPanel/index.native.js +16 -7
  240. package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
  241. package/dist/jsx/FloatingPanel/nestedAction.mjs +11 -0
  242. package/dist/jsx/FloatingPanel/nestedAction.mjs.map +1 -0
  243. package/dist/jsx/FloatingPanel/nestedAction.native.js +42 -0
  244. package/dist/jsx/FloatingPanel/nestedAction.native.js.map +1 -0
  245. package/dist/jsx/InputParts/index.mjs +20 -9
  246. package/dist/jsx/InputParts/index.mjs.map +1 -1
  247. package/dist/jsx/InputParts/index.native.js +20 -11
  248. package/dist/jsx/InputParts/index.native.js.map +1 -1
  249. package/dist/jsx/Wheel/index.mjs +3 -3
  250. package/dist/jsx/Wheel/index.mjs.map +1 -1
  251. package/dist/jsx/Wheel/index.native.js +3 -3
  252. package/dist/jsx/Wheel/index.native.js.map +1 -1
  253. package/dist/jsx/fieldLayout.mjs +3 -3
  254. package/dist/jsx/fieldLayout.mjs.map +1 -1
  255. package/dist/jsx/fieldLayout.native.js +3 -3
  256. package/dist/jsx/fieldLayout.native.js.map +1 -1
  257. package/dist/jsx/fields/Checkbox/CheckboxFieldFrame.mjs +8 -1
  258. package/dist/jsx/fields/Checkbox/CheckboxFieldFrame.mjs.map +1 -1
  259. package/dist/jsx/fields/Checkbox/CheckboxFieldFrame.native.js +10 -1
  260. package/dist/jsx/fields/Checkbox/CheckboxFieldFrame.native.js.map +1 -1
  261. package/dist/jsx/fields/Combobox/index.mjs +36 -29
  262. package/dist/jsx/fields/Combobox/index.mjs.map +1 -1
  263. package/dist/jsx/fields/Combobox/index.native.js +38 -38
  264. package/dist/jsx/fields/Combobox/index.native.js.map +1 -1
  265. package/dist/jsx/fields/DatePicker/Calendar.mjs +6 -1
  266. package/dist/jsx/fields/DatePicker/Calendar.mjs.map +1 -1
  267. package/dist/jsx/fields/DatePicker/Calendar.native.js +6 -1
  268. package/dist/jsx/fields/DatePicker/Calendar.native.js.map +1 -1
  269. package/dist/jsx/fields/DatePicker/DateRangePicker.mjs +2 -2
  270. package/dist/jsx/fields/DatePicker/DateRangePicker.mjs.map +1 -1
  271. package/dist/jsx/fields/DatePicker/DateRangePicker.native.js +2 -2
  272. package/dist/jsx/fields/DatePicker/DateRangePicker.native.js.map +1 -1
  273. package/dist/jsx/fields/DatePicker/DatetimePicker.catalog.mjs +15 -12
  274. package/dist/jsx/fields/DatePicker/DatetimePicker.catalog.mjs.map +1 -1
  275. package/dist/jsx/fields/DatePicker/DatetimePicker.catalog.native.js +16 -12
  276. package/dist/jsx/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
  277. package/dist/jsx/fields/DatePicker/DatetimePicker.native.js +39 -14
  278. package/dist/jsx/fields/DatePicker/DatetimePicker.native.js.map +1 -1
  279. package/dist/jsx/fields/DatePicker/MonthPicker.mjs +10 -3
  280. package/dist/jsx/fields/DatePicker/MonthPicker.mjs.map +1 -1
  281. package/dist/jsx/fields/DatePicker/MonthPicker.native.js +10 -3
  282. package/dist/jsx/fields/DatePicker/MonthPicker.native.js.map +1 -1
  283. package/dist/jsx/fields/DatePicker/MultiDatePicker.mjs +2 -2
  284. package/dist/jsx/fields/DatePicker/MultiDatePicker.mjs.map +1 -1
  285. package/dist/jsx/fields/DatePicker/MultiDatePicker.native.js +2 -2
  286. package/dist/jsx/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
  287. package/dist/jsx/fields/DatePicker/index.native.js +3 -0
  288. package/dist/jsx/fields/DatePicker/index.native.js.map +1 -1
  289. package/dist/jsx/fields/DatePicker/parts.mjs +28 -16
  290. package/dist/jsx/fields/DatePicker/parts.mjs.map +1 -1
  291. package/dist/jsx/fields/DatePicker/parts.native.js +28 -14
  292. package/dist/jsx/fields/DatePicker/parts.native.js.map +1 -1
  293. package/dist/jsx/fields/FileUpload/index.mjs +6 -0
  294. package/dist/jsx/fields/FileUpload/index.mjs.map +1 -1
  295. package/dist/jsx/fields/FileUpload/index.native.js +6 -0
  296. package/dist/jsx/fields/FileUpload/index.native.js.map +1 -1
  297. package/dist/jsx/fields/Geolocation/GeolocationMap.mjs.map +1 -1
  298. package/dist/jsx/fields/Geolocation/GeolocationMap.native.js.map +1 -1
  299. package/dist/jsx/fields/Geolocation/geocoder.mjs +33 -0
  300. package/dist/jsx/fields/Geolocation/geocoder.mjs.map +1 -0
  301. package/dist/jsx/fields/Geolocation/geocoder.native.js +68 -0
  302. package/dist/jsx/fields/Geolocation/geocoder.native.js.map +1 -0
  303. package/dist/jsx/fields/Geolocation/index.mjs +792 -149
  304. package/dist/jsx/fields/Geolocation/index.mjs.map +1 -1
  305. package/dist/jsx/fields/Geolocation/index.native.js +856 -167
  306. package/dist/jsx/fields/Geolocation/index.native.js.map +1 -1
  307. package/dist/jsx/fields/Geolocation/nativeSearchFocus.mjs +19 -0
  308. package/dist/jsx/fields/Geolocation/nativeSearchFocus.mjs.map +1 -0
  309. package/dist/jsx/fields/Geolocation/nativeSearchFocus.native.js +47 -0
  310. package/dist/jsx/fields/Geolocation/nativeSearchFocus.native.js.map +1 -0
  311. package/dist/jsx/fields/Icon/drawnSet.mjs +16 -3
  312. package/dist/jsx/fields/Icon/drawnSet.mjs.map +1 -1
  313. package/dist/jsx/fields/Icon/drawnSet.native.js +20 -3
  314. package/dist/jsx/fields/Icon/drawnSet.native.js.map +1 -1
  315. package/dist/jsx/fields/Icon/index.mjs +93 -18
  316. package/dist/jsx/fields/Icon/index.mjs.map +1 -1
  317. package/dist/jsx/fields/Icon/index.native.js +115 -31
  318. package/dist/jsx/fields/Icon/index.native.js.map +1 -1
  319. package/dist/jsx/fields/Input/index.mjs +5 -12
  320. package/dist/jsx/fields/Input/index.mjs.map +1 -1
  321. package/dist/jsx/fields/Input/index.native.js +5 -13
  322. package/dist/jsx/fields/Input/index.native.js.map +1 -1
  323. package/dist/jsx/fields/PhoneInput/index.mjs +2 -2
  324. package/dist/jsx/fields/PhoneInput/index.mjs.map +1 -1
  325. package/dist/jsx/fields/PhoneInput/index.native.js +2 -2
  326. package/dist/jsx/fields/PhoneInput/index.native.js.map +1 -1
  327. package/dist/jsx/fields/RadioGroup/index.mjs +34 -5
  328. package/dist/jsx/fields/RadioGroup/index.mjs.map +1 -1
  329. package/dist/jsx/fields/RadioGroup/index.native.js +49 -7
  330. package/dist/jsx/fields/RadioGroup/index.native.js.map +1 -1
  331. package/dist/jsx/fields/SearchInput/index.mjs +9 -10
  332. package/dist/jsx/fields/SearchInput/index.mjs.map +1 -1
  333. package/dist/jsx/fields/SearchInput/index.native.js +8 -9
  334. package/dist/jsx/fields/SearchInput/index.native.js.map +1 -1
  335. package/dist/jsx/fields/Select/index.mjs +42 -11
  336. package/dist/jsx/fields/Select/index.mjs.map +1 -1
  337. package/dist/jsx/fields/Select/index.native.js +52 -20
  338. package/dist/jsx/fields/Select/index.native.js.map +1 -1
  339. package/dist/jsx/fields/Stepper/index.mjs +145 -32
  340. package/dist/jsx/fields/Stepper/index.mjs.map +1 -1
  341. package/dist/jsx/fields/Stepper/index.native.js +151 -31
  342. package/dist/jsx/fields/Stepper/index.native.js.map +1 -1
  343. package/dist/jsx/fields/ToggleGroup/index.mjs +35 -2
  344. package/dist/jsx/fields/ToggleGroup/index.mjs.map +1 -1
  345. package/dist/jsx/fields/ToggleGroup/index.native.js +42 -2
  346. package/dist/jsx/fields/ToggleGroup/index.native.js.map +1 -1
  347. package/dist/jsx/index.js +2 -1
  348. package/dist/jsx/index.js.map +1 -1
  349. package/dist/jsx/index.mjs +2 -1
  350. package/dist/jsx/index.mjs.map +1 -1
  351. package/dist/jsx/index.native.js +2 -0
  352. package/dist/jsx/index.native.js.map +1 -1
  353. package/dist/jsx/shared/focusManagement.mjs +24 -11
  354. package/dist/jsx/shared/focusManagement.mjs.map +1 -1
  355. package/dist/jsx/shared/focusManagement.native.js +24 -11
  356. package/dist/jsx/shared/focusManagement.native.js.map +1 -1
  357. package/dist/jsx/shared/nativeA11y.mjs +31 -0
  358. package/dist/jsx/shared/nativeA11y.mjs.map +1 -0
  359. package/dist/jsx/shared/nativeA11y.native.js +61 -0
  360. package/dist/jsx/shared/nativeA11y.native.js.map +1 -0
  361. package/package.json +9 -7
  362. package/src/Button/Button.fontWeight.spec.tsx +75 -0
  363. package/src/Button/Button.labelInk.spec.tsx +21 -18
  364. package/src/Button/Button.outlinedChannel.spec.tsx +46 -0
  365. package/src/Button/Button.sizeLadder.spec.tsx +4 -3
  366. package/src/Button/Button.stories.tsx +20 -0
  367. package/src/Button/index.tsx +30 -7
  368. package/src/Chip/Chip.spec.tsx +3 -1
  369. package/src/Chip/index.tsx +1 -5
  370. package/src/FieldGroup.stories.tsx +3 -7
  371. package/src/FieldLayout.stories.tsx +75 -12
  372. package/src/Fieldset.stories.tsx +3 -7
  373. package/src/FloatingPanel/FloatingPanel.native.spec.tsx +54 -0
  374. package/src/FloatingPanel/FloatingPanel.spec.tsx +109 -0
  375. package/src/FloatingPanel/index.native.tsx +24 -7
  376. package/src/FloatingPanel/index.tsx +80 -13
  377. package/src/FloatingPanel/nestedAction.spec.ts +36 -0
  378. package/src/FloatingPanel/nestedAction.ts +15 -0
  379. package/src/FloatingPanel/panelWidthContract.spec.ts +12 -15
  380. package/src/FormGrid.stories.tsx +5 -11
  381. package/src/FormsIntegration.stories.tsx +2 -6
  382. package/src/InputParts/InputButton.spec.tsx +16 -0
  383. package/src/InputParts/index.tsx +34 -7
  384. package/src/InputParts/layoutGap.spec.tsx +43 -0
  385. package/src/Wheel/Wheel.spec.tsx +16 -0
  386. package/src/Wheel/index.tsx +3 -3
  387. package/src/fieldFamilyStories.radius.spec.tsx +118 -0
  388. package/src/fieldLayout.spec.tsx +42 -1
  389. package/src/fieldLayout.tsx +9 -9
  390. package/src/fields/Barcode/Barcode.spec.tsx +2 -0
  391. package/src/fields/Checkbox/CheckboxFieldFrame.spec.tsx +18 -0
  392. package/src/fields/Checkbox/CheckboxFieldFrame.tsx +6 -1
  393. package/src/fields/Combobox/Combobox.spec.tsx +48 -1
  394. package/src/fields/Combobox/index.tsx +53 -39
  395. package/src/fields/DatePicker/Calendar.tsx +4 -1
  396. package/src/fields/DatePicker/DatePicker.stories.tsx +22 -1
  397. package/src/fields/DatePicker/DateRangePicker.tsx +4 -2
  398. package/src/fields/DatePicker/DatetimePicker.catalog.tsx +21 -19
  399. package/src/fields/DatePicker/DatetimePicker.native.spec.tsx +144 -0
  400. package/src/fields/DatePicker/DatetimePicker.native.tsx +57 -32
  401. package/src/fields/DatePicker/DatetimePicker.stories.tsx +22 -0
  402. package/src/fields/DatePicker/MonthPicker.tsx +8 -3
  403. package/src/fields/DatePicker/MultiDatePicker.tsx +4 -2
  404. package/src/fields/DatePicker/index.native.tsx +1 -0
  405. package/src/fields/DatePicker/parts.spec.tsx +56 -0
  406. package/src/fields/DatePicker/parts.tsx +33 -12
  407. package/src/fields/FileUpload/FileUpload.spec.tsx +29 -0
  408. package/src/fields/FileUpload/index.tsx +6 -0
  409. package/src/fields/Geolocation/Geolocation.spec.tsx +287 -35
  410. package/src/fields/Geolocation/Geolocation.stories.tsx +141 -7
  411. package/src/fields/Geolocation/GeolocationMap.native.tsx +3 -0
  412. package/src/fields/Geolocation/GeolocationMap.tsx +4 -0
  413. package/src/fields/Geolocation/geocoder.spec.ts +59 -0
  414. package/src/fields/Geolocation/geocoder.ts +84 -0
  415. package/src/fields/Geolocation/geocoderSeam.spec.tsx +203 -0
  416. package/src/fields/Geolocation/index.tsx +912 -180
  417. package/src/fields/Geolocation/nativeSearchFocus.spec.ts +34 -0
  418. package/src/fields/Geolocation/nativeSearchFocus.ts +19 -0
  419. package/src/fields/Geolocation/salvage.spec.tsx +398 -0
  420. package/src/fields/Geolocation/storyProviders.spec.tsx +44 -0
  421. package/src/fields/Icon/Icon.spec.tsx +215 -1
  422. package/src/fields/Icon/Icon.stories.tsx +9 -1
  423. package/src/fields/Icon/drawnSet.tsx +44 -4
  424. package/src/fields/Icon/index.tsx +118 -18
  425. package/src/fields/Input/Input.copy.spec.tsx +1 -0
  426. package/src/fields/Input/Input.spec.tsx +50 -0
  427. package/src/fields/Input/Input.stories.tsx +1 -1
  428. package/src/fields/Input/index.tsx +7 -13
  429. package/src/fields/PhoneInput/index.tsx +2 -2
  430. package/src/fields/RadioGroup/RadioGroup.native.spec.tsx +67 -0
  431. package/src/fields/RadioGroup/index.tsx +82 -8
  432. package/src/fields/SearchInput/SearchInput.spec.tsx +14 -0
  433. package/src/fields/SearchInput/index.tsx +5 -6
  434. package/src/fields/Select/Select.spec.tsx +54 -0
  435. package/src/fields/Select/index.tsx +52 -17
  436. package/src/fields/Stepper/Stepper.spec.tsx +34 -1
  437. package/src/fields/Stepper/index.tsx +193 -33
  438. package/src/fields/ToggleGroup/ToggleGroup.native.spec.tsx +73 -0
  439. package/src/fields/ToggleGroup/ToggleGroup.spec.tsx +49 -0
  440. package/src/fields/ToggleGroup/index.tsx +76 -1
  441. package/src/index.ts +9 -0
  442. package/src/shared/focusManagement.spec.tsx +68 -0
  443. package/src/shared/focusManagement.ts +20 -10
  444. package/src/shared/nativeA11y.spec.ts +64 -0
  445. package/src/shared/nativeA11y.ts +80 -0
  446. package/types/Button/index.d.ts +22 -3
  447. package/types/Button/index.d.ts.map +1 -1
  448. package/types/Chip/index.d.ts.map +1 -1
  449. package/types/FloatingPanel/index.d.ts +11 -1
  450. package/types/FloatingPanel/index.d.ts.map +1 -1
  451. package/types/FloatingPanel/index.native.d.ts +2 -1
  452. package/types/FloatingPanel/index.native.d.ts.map +1 -1
  453. package/types/FloatingPanel/nestedAction.d.ts +13 -0
  454. package/types/FloatingPanel/nestedAction.d.ts.map +1 -0
  455. package/types/InputParts/index.d.ts.map +1 -1
  456. package/types/fields/Checkbox/CheckboxFieldFrame.d.ts +46 -2
  457. package/types/fields/Checkbox/CheckboxFieldFrame.d.ts.map +1 -1
  458. package/types/fields/Checkbox/index.d.ts +3 -3
  459. package/types/fields/Combobox/index.d.ts +3 -1
  460. package/types/fields/Combobox/index.d.ts.map +1 -1
  461. package/types/fields/DatePicker/Calendar.d.ts.map +1 -1
  462. package/types/fields/DatePicker/DateRangePicker.d.ts.map +1 -1
  463. package/types/fields/DatePicker/DatetimePicker.catalog.d.ts.map +1 -1
  464. package/types/fields/DatePicker/DatetimePicker.native.d.ts.map +1 -1
  465. package/types/fields/DatePicker/MonthPicker.d.ts.map +1 -1
  466. package/types/fields/DatePicker/MultiDatePicker.d.ts.map +1 -1
  467. package/types/fields/DatePicker/index.native.d.ts.map +1 -1
  468. package/types/fields/DatePicker/parts.d.ts.map +1 -1
  469. package/types/fields/FileUpload/index.d.ts.map +1 -1
  470. package/types/fields/Geolocation/GeolocationMap.d.ts +4 -0
  471. package/types/fields/Geolocation/GeolocationMap.d.ts.map +1 -1
  472. package/types/fields/Geolocation/GeolocationMap.native.d.ts +3 -0
  473. package/types/fields/Geolocation/GeolocationMap.native.d.ts.map +1 -1
  474. package/types/fields/Geolocation/geocoder.d.ts +53 -0
  475. package/types/fields/Geolocation/geocoder.d.ts.map +1 -0
  476. package/types/fields/Geolocation/index.d.ts +18 -2
  477. package/types/fields/Geolocation/index.d.ts.map +1 -1
  478. package/types/fields/Geolocation/nativeSearchFocus.d.ts +8 -0
  479. package/types/fields/Geolocation/nativeSearchFocus.d.ts.map +1 -0
  480. package/types/fields/Icon/drawnSet.d.ts.map +1 -1
  481. package/types/fields/Icon/index.d.ts.map +1 -1
  482. package/types/fields/Input/index.d.ts.map +1 -1
  483. package/types/fields/PhoneInput/index.d.ts.map +1 -1
  484. package/types/fields/RadioGroup/index.d.ts +30 -3
  485. package/types/fields/RadioGroup/index.d.ts.map +1 -1
  486. package/types/fields/SearchInput/index.d.ts.map +1 -1
  487. package/types/fields/Select/index.d.ts.map +1 -1
  488. package/types/fields/Stepper/index.d.ts.map +1 -1
  489. package/types/fields/ToggleGroup/index.d.ts +31 -2
  490. package/types/fields/ToggleGroup/index.d.ts.map +1 -1
  491. package/types/index.d.ts +2 -0
  492. package/types/index.d.ts.map +1 -1
  493. package/types/shared/focusManagement.d.ts +2 -2
  494. package/types/shared/focusManagement.d.ts.map +1 -1
  495. package/types/shared/nativeA11y.d.ts +61 -0
  496. package/types/shared/nativeA11y.d.ts.map +1 -0
  497. package/src/router.d.ts +0 -16
@@ -1,23 +1,33 @@
1
- import { ensureFocusVisibleRing, useResolvedKnobs, type KnobProps } from "@multiplatform.one/theme";
2
- import { NavigationArrowIcon } from "@phosphor-icons/react";
1
+ import {
2
+ ensureFocusVisibleRing,
3
+ pressTargetHitSlop,
4
+ useResolvedKnobs,
5
+ useGlyphColor,
6
+ type KnobProps,
7
+ } from "@multiplatform.one/theme";
8
+ import { MagnifyingGlassIcon, MapPinIcon, NavigationArrowIcon, XIcon } from "@phosphor-icons/react";
3
9
  import type { KeyboardEvent, MutableRefObject, ReactNode } from "react";
4
- import { useCallback, useEffect, useRef, useState } from "react";
10
+ import { useCallback, useEffect, useId, useRef, useState } from "react";
5
11
  import type { LabelProps, SizeTokens } from "tamagui";
6
- import { Spinner, Text, View, XStack, YStack, isWeb } from "tamagui";
12
+ import { ScrollView, Spinner, Text, View, XStack, YStack, isWeb } from "tamagui";
7
13
  import { AdaptivePopup } from "../../AdaptivePopup";
8
14
  import { Button } from "../../Button";
9
15
  import { Field, FieldLayout } from "../../fieldLayout";
10
- import { FloatingPanel } from "../../FloatingPanel";
11
- import { GeolocationMap } from "./GeolocationMap";
12
- import { detectGeoShape, formatGeoValue, type GeoValue, parseGeoValue } from "./geoValue";
16
+ import { FloatingPanel, useViewportGtSm } from "../../FloatingPanel";
13
17
  import { Input as InputParts } from "../../InputParts";
14
18
  import { Skeleton } from "../../Skeleton";
19
+ import { GeolocationMap } from "./GeolocationMap";
20
+ import { createNativeSearchFocus } from "./nativeSearchFocus";
21
+ import { detectGeoShape, formatGeoValue, parseGeoValue } from "./geoValue";
15
22
  import {
16
23
  queryPermissionState,
17
24
  type CapabilityPreflightResult,
18
25
  } from "../../shared/capabilityPreflight";
19
26
  import { formCommonColors, formInputColors } from "../../shared/colorRamps";
20
27
  import { t } from "../../shared/t";
28
+ import { pressSlopProps } from "../../shared/pressSlopProps";
29
+ import { useIsInTableCell } from "../../shared/tableCellContext";
30
+ import { zIndex } from "../../shared/zIndex";
21
31
  import {
22
32
  clampRadiusForLargeComponent,
23
33
  getFieldError,
@@ -27,8 +37,44 @@ import {
27
37
  useResolvedValidators,
28
38
  } from "../../shared/utils";
29
39
  import type { AnyFormApi } from "../../types";
40
+ import type { GeocodeFn, GeocodeResult, Geocoder, ReverseGeocodeFn } from "./geocoder";
41
+ import {
42
+ clampLat,
43
+ clampLng,
44
+ formatCoordPair,
45
+ formatPlace,
46
+ parseLatLngPair,
47
+ resolveGeocoder,
48
+ } from "./geocoder";
49
+
50
+ export type {
51
+ GeocodeFn,
52
+ GeocodeQuery,
53
+ GeocodeResult,
54
+ Geocoder,
55
+ ReverseGeocodeFn,
56
+ ReverseGeocodeQuery,
57
+ } from "./geocoder";
58
+ export {
59
+ clampLat,
60
+ clampLng,
61
+ formatCoordPair,
62
+ formatPlace,
63
+ parseLatLngPair,
64
+ resolveGeocoder,
65
+ } from "./geocoder";
30
66
 
31
67
  type LocatePhase = "idle" | "locating" | "error";
68
+ type SearchStatus = "idle" | "searching" | "results" | "empty" | "error";
69
+ type ResolvedAddress = { key: string; label: string };
70
+
71
+ function coordinateKey({ lat, lng }: { lat: number; lng: number }): string {
72
+ return `${lat},${lng}`;
73
+ }
74
+
75
+ const SEARCH_DEBOUNCE_MS = 300;
76
+ const COORD_LINE_HEIGHT = 28;
77
+ const RESULTS_PANEL_SHADOW = "0px 8px 28px rgba(0,0,0,0.12), 0px 2px 6px rgba(0,0,0,0.04)";
32
78
 
33
79
  /**
34
80
  * Geolocation capability pre-flight (LC-19 CAPABILITY-PREFLIGHT). Runs BEFORE
@@ -75,17 +121,6 @@ function mapGeolocationError(
75
121
  return err.message || t("Failed to get location");
76
122
  }
77
123
 
78
- function clampLat(val: number): number {
79
- return Math.max(-90, Math.min(90, val));
80
- }
81
- function clampLng(val: number): number {
82
- return Math.max(-180, Math.min(180, val));
83
- }
84
-
85
- function formatCoordChip(lat: number, lng: number): string {
86
- return `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
87
- }
88
-
89
124
  /**
90
125
  * Parse a coordinate draft. Returns undefined while the user is mid-keystroke
91
126
  * (empty, lone minus, trailing decimal) so we don't clobber the bound value.
@@ -144,7 +179,6 @@ function CoordinatesPanel({
144
179
  [onCommit],
145
180
  );
146
181
 
147
- /** Dismiss/blur flush — more permissive than live parse (accepts trailing "."). */
148
182
  const flushDraft = useCallback(() => {
149
183
  const rawLat = Number.parseFloat(latTextRef.current);
150
184
  const rawLng = Number.parseFloat(lngTextRef.current);
@@ -269,8 +303,159 @@ export interface GeolocationMapProps {
269
303
  knobProps: KnobProps;
270
304
  }
271
305
 
272
- /** Overlay buttons sit inside the map's overflow:hidden — inset ring (LC-71). */
273
- const overlayFocusRing = ensureFocusVisibleRing({ outlineOffset: -2 });
306
+ /** Overlay buttons sit inside the map — 2px ring at offset 0 (media-13). */
307
+ const overlayFocusRing = ensureFocusVisibleRing({ outlineOffset: 0 });
308
+
309
+ function ResultRow({
310
+ testID,
311
+ label,
312
+ description,
313
+ onPress,
314
+ disabled,
315
+ }: {
316
+ testID?: string;
317
+ label: string;
318
+ description?: string;
319
+ onPress: () => void;
320
+ disabled?: boolean;
321
+ }) {
322
+ return (
323
+ <YStack
324
+ testID={testID}
325
+ role="option"
326
+ aria-selected={false}
327
+ aria-disabled={disabled || undefined}
328
+ {...(isWeb
329
+ ? {
330
+ tabIndex: disabled ? -1 : 0,
331
+ onKeyDown: (event: KeyboardEvent<HTMLElement>) => {
332
+ if (disabled) return;
333
+ if (event.key === "Enter" || event.key === " ") {
334
+ event.preventDefault();
335
+ onPress();
336
+ } else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
337
+ event.preventDefault();
338
+ const rows = [
339
+ ...(event.currentTarget.parentElement?.querySelectorAll<HTMLElement>(
340
+ "[role='option']:not([aria-disabled='true'])",
341
+ ) ?? []),
342
+ ];
343
+ const index = rows.indexOf(event.currentTarget);
344
+ rows[
345
+ (index + (event.key === "ArrowDown" ? 1 : -1) + rows.length) % rows.length
346
+ ]?.focus();
347
+ }
348
+ },
349
+ }
350
+ : {})}
351
+ minHeight={description ? 62 : 44}
352
+ justifyContent="center"
353
+ paddingHorizontal="$3"
354
+ paddingVertical={description ? "$2" : 0}
355
+ cursor={disabled ? "not-allowed" : "pointer"}
356
+ hoverStyle={disabled ? undefined : { backgroundColor: "$color3" }}
357
+ pressStyle={disabled ? undefined : { backgroundColor: "$color3" }}
358
+ onPress={disabled ? undefined : onPress}
359
+ opacity={disabled ? 0.5 : 1}
360
+ >
361
+ <Text fontSize="$4" lineHeight={25} color="$color12">
362
+ {label}
363
+ </Text>
364
+ {description ? (
365
+ <Text fontSize="$3" lineHeight={24} color={formCommonColors.muted}>
366
+ {description}
367
+ </Text>
368
+ ) : null}
369
+ </YStack>
370
+ );
371
+ }
372
+
373
+ function StatusRow({ children, testID }: { children: ReactNode; testID?: string }) {
374
+ return (
375
+ <XStack testID={testID} minHeight={44} alignItems="center" paddingHorizontal="$3" role="status">
376
+ <Text fontSize="$4" lineHeight={25} color={formCommonColors.muted}>
377
+ {children}
378
+ </Text>
379
+ </XStack>
380
+ );
381
+ }
382
+
383
+ function SearchResultsList({
384
+ listId,
385
+ status,
386
+ query,
387
+ results,
388
+ pair,
389
+ onLocate,
390
+ onPick,
391
+ onPickPair,
392
+ locateDisabled,
393
+ }: {
394
+ listId: string;
395
+ status: SearchStatus;
396
+ query: string;
397
+ results: GeocodeResult[];
398
+ pair: { lat: number; lng: number } | null;
399
+ onLocate: () => void;
400
+ onPick: (result: GeocodeResult) => void;
401
+ onPickPair: (lat: number, lng: number) => void;
402
+ locateDisabled?: boolean;
403
+ }) {
404
+ const showLocate = status === "idle" || status === "results" || pair != null;
405
+ return (
406
+ <YStack
407
+ testID="geolocation-results"
408
+ id={listId}
409
+ overflow="hidden"
410
+ {...(isWeb
411
+ ? // role="listbox" is a valid ARIA role but outside RN's `Role` union;
412
+ // this web-only bag forwards it to the DOM node (house Record cast).
413
+ ({ role: "listbox" } as Record<string, unknown>)
414
+ : undefined)}
415
+ >
416
+ {showLocate ? (
417
+ <ResultRow
418
+ testID="geolocation-use-location"
419
+ label={t("Use my current location")}
420
+ onPress={onLocate}
421
+ disabled={locateDisabled}
422
+ />
423
+ ) : null}
424
+ {status === "searching" ? (
425
+ <StatusRow testID="geolocation-search-status">{t("Searching…")}</StatusRow>
426
+ ) : null}
427
+ {status === "error" ? (
428
+ <StatusRow testID="geolocation-search-status">
429
+ {t("Search is unavailable — check your connection")}
430
+ </StatusRow>
431
+ ) : null}
432
+ {status === "empty" && !pair ? (
433
+ <StatusRow testID="geolocation-search-status">
434
+ {t('No places match "{{query}}"', { query })}
435
+ </StatusRow>
436
+ ) : null}
437
+ {pair ? (
438
+ <ResultRow
439
+ testID="geolocation-coord-result"
440
+ label={t("Go to {{pair}}", { pair: formatCoordPair(pair.lat, pair.lng) })}
441
+ description={t("Exact coordinates — no address")}
442
+ onPress={() => onPickPair(pair.lat, pair.lng)}
443
+ />
444
+ ) : null}
445
+ {status === "results"
446
+ ? results.map((result, index) => (
447
+ <ResultRow
448
+ key={`${result.lat},${result.lng},${index}`}
449
+ testID={`geolocation-result-${index}`}
450
+ label={result.label}
451
+ description={result.description}
452
+ onPress={() => onPick(result)}
453
+ />
454
+ ))
455
+ : null}
456
+ </YStack>
457
+ );
458
+ }
274
459
 
275
460
  function GeoMapSurface({
276
461
  id,
@@ -293,7 +478,7 @@ function GeoMapSurface({
293
478
  mapHeight,
294
479
  }: {
295
480
  id?: string;
296
- coordinates: GeoValue | null;
481
+ coordinates: { lat: number; lng: number } | null;
297
482
  onCommitCoordinates: (lat: number, lng: number) => void;
298
483
  onUseMyLocation: () => void;
299
484
  onRetryLocate: () => void;
@@ -322,13 +507,8 @@ function GeoMapSurface({
322
507
  const interactive = !disabled && !readOnly;
323
508
  const cappedRadius = clampRadiusForLargeComponent(knobProps.borderRadius.borderRadius);
324
509
 
325
- // A disabled field opens nothing: the Button below is pointerEvents-none
326
- // when disabled, but Expand is opened programmatically (AdaptivePopup here
327
- // takes no `trigger`), so the guard has to live in the handler too.
328
510
  const handleExpand = expandIcon ? () => !disabled && setExpanded(true) : undefined;
329
511
 
330
- // LC-21 EDITOR: outside/Escape closes; flush any in-progress draft so nothing is lost.
331
- // Live-valid parses already committed — close is then dismiss-only.
332
512
  const handleCoordsOpenChange = (open: boolean) => {
333
513
  if (!open) {
334
514
  coordsFlushRef.current?.();
@@ -338,7 +518,7 @@ function GeoMapSurface({
338
518
 
339
519
  const handleMapKeyDown = (event: KeyboardEvent) => {
340
520
  if (!interactive) return;
341
- if (event.key === "Enter") {
521
+ if (event.key === "Enter" && hasValue) {
342
522
  event.preventDefault();
343
523
  setCoordsOpen(true);
344
524
  return;
@@ -366,8 +546,71 @@ function GeoMapSurface({
366
546
  })
367
547
  : null;
368
548
 
549
+ const coordLine =
550
+ readOnly || disabled ? (
551
+ <XStack
552
+ testID="geolocation-coordline"
553
+ alignItems="center"
554
+ minHeight={COORD_LINE_HEIGHT}
555
+ paddingHorizontal="$2"
556
+ hitSlop={pressTargetHitSlop(COORD_LINE_HEIGHT)}
557
+ >
558
+ <Text fontFamily="$mono" fontSize={12} lineHeight={22} color={formCommonColors.muted}>
559
+ {hasValue ? formatCoordPair(mapLat, mapLng) : t("Enter coordinates")}
560
+ </Text>
561
+ </XStack>
562
+ ) : (
563
+ <FloatingPanel
564
+ open={coordsOpen}
565
+ onOpenChange={handleCoordsOpenChange}
566
+ disabled={disabled}
567
+ fitContent={false}
568
+ sizing="fill"
569
+ contentPadding="none"
570
+ trigger={
571
+ <XStack
572
+ testID="geolocation-coordline"
573
+ alignItems="center"
574
+ minHeight={COORD_LINE_HEIGHT}
575
+ paddingHorizontal="$2"
576
+ cursor="pointer"
577
+ {...pressSlopProps(COORD_LINE_HEIGHT, true)}
578
+ hoverStyle={{ backgroundColor: "$color3" }}
579
+ focusVisibleStyle={overlayFocusRing}
580
+ {...(isWeb
581
+ ? {
582
+ tabIndex: 0,
583
+ role: "button",
584
+ "aria-label": t("Edit coordinates"),
585
+ onKeyDown: (event: KeyboardEvent) => {
586
+ if (event.key === "Enter" || event.key === " ") {
587
+ event.preventDefault();
588
+ setCoordsOpen(true);
589
+ }
590
+ },
591
+ }
592
+ : {})}
593
+ >
594
+ <Text fontFamily="$mono" fontSize={12} lineHeight={22} color={formCommonColors.muted}>
595
+ {hasValue ? formatCoordPair(mapLat, mapLng) : t("Enter coordinates")}
596
+ </Text>
597
+ </XStack>
598
+ }
599
+ >
600
+ <CoordinatesPanel
601
+ lat={mapLat}
602
+ lng={mapLng}
603
+ knobProps={knobProps}
604
+ required={required}
605
+ hasError={hasError}
606
+ flushRef={coordsFlushRef}
607
+ onCommit={onCommitCoordinates}
608
+ />
609
+ </FloatingPanel>
610
+ );
611
+
369
612
  return (
370
- <>
613
+ <YStack gap="$2">
371
614
  <YStack
372
615
  testID="geolocation-map"
373
616
  id={id}
@@ -386,7 +629,7 @@ function GeoMapSurface({
386
629
  tabIndex: 0,
387
630
  role: "application",
388
631
  "aria-label": t("Location map"),
389
- "aria-valuetext": hasValue ? formatCoordChip(mapLat, mapLng) : t("No location set"),
632
+ "aria-valuetext": hasValue ? formatCoordPair(mapLat, mapLng) : t("No location set"),
390
633
  onKeyDown: handleMapKeyDown,
391
634
  }
392
635
  : {})}
@@ -403,66 +646,36 @@ function GeoMapSurface({
403
646
  />
404
647
  )}
405
648
 
406
- {/* Chip is the coordinates editor trigger (Apple/Google compact pattern). */}
407
- {readOnly ? (
408
- <View
649
+ {!hasValue && !locateErrorOpen ? (
650
+ <YStack
651
+ testID="geolocation-empty"
409
652
  position="absolute"
410
- top="$2"
411
- insetInlineStart="$2"
653
+ top={0}
654
+ right={0}
655
+ bottom={0}
656
+ left={0}
412
657
  zIndex={2}
413
- backgroundColor="$background"
414
- opacity={0.92}
415
- paddingHorizontal="$2"
416
- paddingVertical="$1"
417
- {...knobProps.borderRadius}
658
+ alignItems="center"
659
+ justifyContent="center"
660
+ gap="$2"
661
+ pointerEvents="box-none"
418
662
  >
419
- <Text
420
- testID="geolocation-coords-chip"
421
- fontSize="$1"
422
- {...knobProps.body}
423
- color={formCommonColors.muted}
424
- >
425
- {hasValue ? formatCoordChip(mapLat, mapLng) : t("No location set")}
663
+ <Text {...knobProps.body} color={formCommonColors.muted} pointerEvents="none">
664
+ {t("No location set")}
426
665
  </Text>
427
- </View>
428
- ) : (
429
- <View position="absolute" top="$2" insetInlineStart="$2" zIndex={2}>
430
- <FloatingPanel
431
- open={coordsOpen}
432
- onOpenChange={handleCoordsOpenChange}
433
- disabled={disabled}
434
- fitContent
435
- sizing="fill"
436
- contentPadding="none"
437
- trigger={
438
- <Button
439
- size="$2"
440
- disabled={disabled}
441
- backgroundColor="$background"
442
- opacity={0.92}
443
- testID="geolocation-coords-chip"
444
- aria-label={t("Edit coordinates")}
445
- focusVisibleStyle={overlayFocusRing}
446
- onPress={isWeb && !disabled ? () => setCoordsOpen(true) : undefined}
447
- >
448
- <Button.Text>
449
- {hasValue ? formatCoordChip(mapLat, mapLng) : t("Set location")}
450
- </Button.Text>
451
- </Button>
452
- }
453
- >
454
- <CoordinatesPanel
455
- lat={mapLat}
456
- lng={mapLng}
457
- knobProps={knobProps}
458
- required={required}
459
- hasError={hasError}
460
- flushRef={coordsFlushRef}
461
- onCommit={onCommitCoordinates}
462
- />
463
- </FloatingPanel>
464
- </View>
465
- )}
666
+ {!readOnly && !disabled ? (
667
+ <Button
668
+ size="$4"
669
+ onPress={onUseMyLocation}
670
+ disabled={locating}
671
+ testID="geolocation-empty-locate"
672
+ aria-label={t("Use my current location")}
673
+ >
674
+ <Button.Text>{t("Use my current location")}</Button.Text>
675
+ </Button>
676
+ ) : null}
677
+ </YStack>
678
+ ) : null}
466
679
 
467
680
  {handleExpand && expandIcon && (
468
681
  <View position="absolute" top="$2" insetInlineEnd="$2" zIndex={2}>
@@ -472,16 +685,15 @@ function GeoMapSurface({
472
685
  disabled={disabled}
473
686
  onPress={handleExpand}
474
687
  backgroundColor="$background"
475
- opacity={0.92}
476
688
  aria-label={t("Expand map")}
477
689
  focusVisibleStyle={overlayFocusRing}
690
+ hitSlop={pressTargetHitSlop(32)}
478
691
  >
479
692
  {expandIcon}
480
693
  </Button>
481
694
  </View>
482
695
  )}
483
696
 
484
- {/* Locate overlay (bottom-end) — LC-19 preflight runs on press */}
485
697
  {!readOnly && !locateErrorOpen && (
486
698
  <View position="absolute" bottom="$2" insetInlineEnd="$2" zIndex={2}>
487
699
  <Button
@@ -490,18 +702,14 @@ function GeoMapSurface({
490
702
  disabled={disabled || locating}
491
703
  onPress={onUseMyLocation}
492
704
  backgroundColor="$background"
493
- opacity={0.92}
494
705
  icon={locating ? <Spinner size="small" /> : locationIcon}
495
706
  aria-label={locating ? t("Locating") : t("Locate")}
496
707
  focusVisibleStyle={overlayFocusRing}
708
+ hitSlop={pressTargetHitSlop(32)}
497
709
  />
498
710
  </View>
499
711
  )}
500
712
 
501
- {/*
502
- LC-19: locate error stays on the map (Retry + Close) — never
503
- mount-fail-unmount, never a layout-jumping sibling under the field.
504
- */}
505
713
  {locateErrorOpen && (
506
714
  <YStack
507
715
  testID="geolocation-locate-panel"
@@ -531,6 +739,7 @@ function GeoMapSurface({
531
739
  </YStack>
532
740
  )}
533
741
  </YStack>
742
+ {coordLine}
534
743
 
535
744
  {expandIcon && (
536
745
  <AdaptivePopup
@@ -549,7 +758,7 @@ function GeoMapSurface({
549
758
  />
550
759
  </AdaptivePopup>
551
760
  )}
552
- </>
761
+ </YStack>
553
762
  );
554
763
  }
555
764
 
@@ -564,7 +773,7 @@ export interface GeolocationProps {
564
773
  required?: boolean;
565
774
  size?: SizeTokens;
566
775
  id?: string;
567
- /** JSON string: {"lat": number, "lng": number} */
776
+ /** JSON string: compact lat/lng or a stock Frappe FeatureCollection. */
568
777
  value?: string;
569
778
  defaultValue?: string;
570
779
  onChange?: (value: string) => void;
@@ -585,12 +794,65 @@ export interface GeolocationProps {
585
794
  skeleton?: boolean;
586
795
  /** When true, applies compact density (tighter layout gaps; control size unchanged) */
587
796
  compact?: boolean;
797
+ /**
798
+ * Forward geocode. Injected — the field never ships a default provider
799
+ * (R4). Without this, search is disabled and the map / locate / coordinate
800
+ * paths stay intact.
801
+ */
802
+ geocode?: GeocodeFn;
803
+ /** Reverse geocode for a set pin. Injected; the pair is the fallback face. */
804
+ reverseGeocode?: ReverseGeocodeFn;
805
+ /**
806
+ * Convenience bag for `geocode` + `reverseGeocode`. Explicit props win.
807
+ * Any object satisfying `Geocoder` works. No provider is constructed for you.
808
+ */
809
+ geocoder?: Geocoder;
588
810
  }
589
811
 
590
- // Package icon default (UX-010): same glyph the frappe-ui adapter supplies —
591
- // bare use renders a complete locate affordance. Module-level element reuse
592
- // is safe (immutable descriptor).
593
- const defaultLocationIcon = <NavigationArrowIcon size={16} />;
812
+ function CellFace({
813
+ address,
814
+ coordinates,
815
+ onOpen,
816
+ }: {
817
+ address: string | null;
818
+ onOpen?: () => void;
819
+ coordinates: { lat: number; lng: number } | null;
820
+ }) {
821
+ const glyphColor = useGlyphColor();
822
+ const text = address
823
+ ? address
824
+ : coordinates
825
+ ? formatCoordPair(coordinates.lat, coordinates.lng)
826
+ : t("No location set");
827
+ return (
828
+ <XStack
829
+ testID="geolocation-cell-face"
830
+ {...(isWeb && onOpen
831
+ ? {
832
+ tabIndex: 0,
833
+ role: "button",
834
+ "aria-label": t("Edit location"),
835
+ onKeyDown: (event: KeyboardEvent) => {
836
+ if (event.key === "Enter" || event.key === " ") {
837
+ event.preventDefault();
838
+ onOpen();
839
+ }
840
+ },
841
+ }
842
+ : {})}
843
+ alignItems="center"
844
+ gap="$2"
845
+ minHeight={44}
846
+ minWidth={0}
847
+ flex={1}
848
+ >
849
+ <MapPinIcon size={14} color={glyphColor} />
850
+ <Text fontSize="$3" lineHeight={24} color="$color12" numberOfLines={1} flex={1} minWidth={0}>
851
+ {text}
852
+ </Text>
853
+ </XStack>
854
+ );
855
+ }
594
856
 
595
857
  export function Geolocation({
596
858
  name,
@@ -609,25 +871,102 @@ export function Geolocation({
609
871
  onBlur,
610
872
  disabled,
611
873
  readOnly,
612
- locationIcon = defaultLocationIcon,
874
+ locationIcon,
613
875
  expandIcon,
614
876
  renderMap,
615
877
  skeleton,
616
878
  compact,
879
+ geocode: geocodeProp,
880
+ reverseGeocode: reverseGeocodeProp,
881
+ geocoder,
617
882
  }: GeolocationProps) {
618
883
  const { resolvedForm, knobProps, id } = useFormField({ form: formProp, id: idProp, compact });
619
884
  const resolvedValidators = useResolvedValidators(required, validators, label, name);
885
+ const glyphColor = useGlyphColor();
886
+ const resolvedLocationIcon =
887
+ locationIcon === undefined ? (
888
+ <NavigationArrowIcon size={16} color={glyphColor} />
889
+ ) : (
890
+ locationIcon
891
+ );
892
+ const inTableCell = useIsInTableCell();
893
+ const viewportGtSm = useViewportGtSm();
894
+ const shouldUseSheet = !isWeb || !viewportGtSm;
895
+ const { geocode, reverseGeocode } = resolveGeocoder({
896
+ geocode: geocodeProp,
897
+ reverseGeocode: reverseGeocodeProp,
898
+ geocoder,
899
+ });
900
+ const searchEnabled = Boolean(geocode) && !disabled && !readOnly;
901
+ const listId = useId();
902
+
620
903
  const [locatePhase, setLocatePhase] = useState<LocatePhase>("idle");
621
904
  const [locateError, setLocateError] = useState<string | null>(null);
622
- /** Pre-flight field error (surface never mounts). Cleared on next successful pre-flight. */
623
905
  const [preflightError, setPreflightError] = useState<string | undefined>();
906
+ const [address, setAddress] = useState<ResolvedAddress | null>(null);
907
+ const [query, setQuery] = useState("");
908
+ const [resultsOpen, setResultsOpen] = useState(false);
909
+ const [searchStatus, setSearchStatus] = useState<SearchStatus>("idle");
910
+ const [results, setResults] = useState<GeocodeResult[]>([]);
911
+ const [cellOpen, setCellOpen] = useState(false);
624
912
  const preflightingRef = useRef(false);
625
913
  const onValueChangeRef = useRef<(value: string) => void>(() => {});
626
914
  // MPO-100: the value being replaced, so an async locate writes back in the
627
915
  // shape the doc arrived in instead of forcing this repo's compact shape.
628
916
  const currentValueRef = useRef<string>("");
917
+ const searchGenRef = useRef(0);
918
+ const reverseGenRef = useRef(0);
919
+ const searchRootRef = useRef<HTMLElement | null>(null);
920
+ const searchInputRef = useRef<HTMLInputElement | null>(null);
921
+ const sheetSearchInputRef = useRef<HTMLInputElement | null>(null);
922
+ const closingSearchRef = useRef(false);
923
+ const [nativeSearchFocus] = useState(createNativeSearchFocus);
924
+ // The input owns return focus. FloatingPanel's delayed restore would run
925
+ // onFocus after dismissal and reopen its own results sheet.
926
+ const skipResultsReturnFocusRef = useRef(true);
927
+ const closeSearch = useCallback(
928
+ (restoreFocus = true) => {
929
+ if (!isWeb) nativeSearchFocus.dismiss();
930
+ closingSearchRef.current = true;
931
+ setResultsOpen(false);
932
+ if (isWeb && restoreFocus) searchInputRef.current?.focus({ preventScroll: true });
933
+ closingSearchRef.current = false;
934
+ },
935
+ [nativeSearchFocus],
936
+ );
937
+ const queryIsAddressRef = useRef(true);
938
+ const selectedPlaceRef = useRef<{ key: string; place: GeocodeResult } | null>(null);
939
+ const locateGenRef = useRef(0);
940
+ const interactiveRef = useRef(!disabled && !readOnly);
941
+ interactiveRef.current = !disabled && !readOnly;
942
+ const cancelLocate = useCallback(() => {
943
+ ++locateGenRef.current;
944
+ setLocatePhase("idle");
945
+ setLocateError(null);
946
+ }, []);
947
+ useEffect(() => {
948
+ if (disabled || readOnly) cancelLocate();
949
+ }, [disabled, readOnly, cancelLocate]);
950
+ useEffect(
951
+ () => () => {
952
+ ++locateGenRef.current;
953
+ ++reverseGenRef.current;
954
+ ++searchGenRef.current;
955
+ },
956
+ [],
957
+ );
958
+
959
+ const applyAddress = useCallback((next: ResolvedAddress | null, intoQuery: boolean) => {
960
+ setAddress(next);
961
+ if (intoQuery) {
962
+ queryIsAddressRef.current = true;
963
+ setQuery(next?.label ?? "");
964
+ }
965
+ }, []);
629
966
 
630
967
  const requestPosition = useCallback(() => {
968
+ if (!interactiveRef.current) return;
969
+ const gen = ++locateGenRef.current;
631
970
  if (!navigator?.geolocation) {
632
971
  setLocateError(t("Geolocation unavailable: not supported in this browser."));
633
972
  setLocatePhase("error");
@@ -637,32 +976,35 @@ export function Geolocation({
637
976
  setLocateError(null);
638
977
  navigator.geolocation.getCurrentPosition(
639
978
  (position) => {
979
+ if (gen !== locateGenRef.current || !interactiveRef.current) return;
980
+ selectedPlaceRef.current = null;
981
+ queryIsAddressRef.current = true;
640
982
  const lat = clampLat(position.coords.latitude);
641
983
  const lng = clampLng(position.coords.longitude);
642
984
  const previous = currentValueRef.current;
643
985
  onValueChangeRef.current(formatGeoValue({ lat, lng }, detectGeoShape(previous), previous));
644
986
  setLocatePhase("idle");
645
987
  setLocateError(null);
988
+ closeSearch();
989
+ setCellOpen(false);
646
990
  },
647
991
  (err) => {
648
- // Surface stays OPEN: error renders inside with Retry + Close.
992
+ if (gen !== locateGenRef.current || !interactiveRef.current) return;
649
993
  setLocateError(mapGeolocationError(err));
650
994
  setLocatePhase("error");
651
995
  },
652
996
  { enableHighAccuracy: true, timeout: 10000 },
653
997
  );
654
- }, []);
998
+ }, [closeSearch]);
655
999
 
656
- // LC-19 CAPABILITY-PREFLIGHT: verify geolocation availability BEFORE presenting
657
- // the locate surface. Pre-flight failure renders an inline field error and the
658
- // surface never mounts. The Locate button stays enabled — pressing it again
659
- // re-runs the pre-flight.
660
1000
  const startLocate = useCallback(async () => {
661
1001
  if (disabled || readOnly) return;
662
1002
  if (preflightingRef.current || locatePhase === "locating") return;
663
1003
  preflightingRef.current = true;
1004
+ const gen = ++locateGenRef.current;
664
1005
  try {
665
1006
  const result = await preflightGeolocation();
1007
+ if (gen !== locateGenRef.current || !interactiveRef.current) return;
666
1008
  if (result.ok) {
667
1009
  setPreflightError(undefined);
668
1010
  requestPosition();
@@ -676,6 +1018,102 @@ export function Geolocation({
676
1018
  }
677
1019
  }, [disabled, readOnly, locatePhase, requestPosition]);
678
1020
 
1021
+ const runReverse = useCallback(
1022
+ async (lat?: number, lng?: number) => {
1023
+ const gen = ++reverseGenRef.current;
1024
+ if (lat === undefined || lng === undefined) {
1025
+ selectedPlaceRef.current = null;
1026
+ applyAddress(null, queryIsAddressRef.current);
1027
+ return;
1028
+ }
1029
+ const key = coordinateKey({ lat, lng });
1030
+ const selected = selectedPlaceRef.current;
1031
+ if (selected?.key === key) {
1032
+ applyAddress({ key, label: formatPlace(selected.place) }, queryIsAddressRef.current);
1033
+ return;
1034
+ }
1035
+ // A new bound pair must not inherit the old address while it resolves.
1036
+ // An independent typed query remains a draft across this transition.
1037
+ applyAddress(null, queryIsAddressRef.current);
1038
+ if (!reverseGeocode) {
1039
+ return;
1040
+ }
1041
+ try {
1042
+ const result = await reverseGeocode({ lat, lng });
1043
+ if (gen !== reverseGenRef.current) return;
1044
+ applyAddress(
1045
+ result ? { key, label: formatPlace(result) } : null,
1046
+ queryIsAddressRef.current,
1047
+ );
1048
+ } catch {
1049
+ if (gen !== reverseGenRef.current) return;
1050
+ applyAddress(null, queryIsAddressRef.current);
1051
+ }
1052
+ },
1053
+ [reverseGeocode, applyAddress],
1054
+ );
1055
+
1056
+ const runSearch = useCallback(
1057
+ async (text: string) => {
1058
+ if (!geocode) return;
1059
+ const gen = ++searchGenRef.current;
1060
+ setSearchStatus("searching");
1061
+ try {
1062
+ const next = await geocode({ query: text });
1063
+ if (gen !== searchGenRef.current) return;
1064
+ setResults(next);
1065
+ setSearchStatus(next.length > 0 ? "results" : "empty");
1066
+ } catch {
1067
+ if (gen !== searchGenRef.current) return;
1068
+ setResults([]);
1069
+ setSearchStatus("error");
1070
+ }
1071
+ },
1072
+ [geocode],
1073
+ );
1074
+
1075
+ useEffect(() => {
1076
+ ++searchGenRef.current;
1077
+ if (!searchEnabled || !resultsOpen) return;
1078
+ const trimmed = query.trim();
1079
+ if (queryIsAddressRef.current) return;
1080
+ if (!trimmed) {
1081
+ setSearchStatus("idle");
1082
+ setResults([]);
1083
+ return;
1084
+ }
1085
+ if (parseLatLngPair(trimmed)) {
1086
+ setSearchStatus("idle");
1087
+ setResults([]);
1088
+ return;
1089
+ }
1090
+ const handle = setTimeout(() => {
1091
+ void runSearch(trimmed);
1092
+ }, SEARCH_DEBOUNCE_MS);
1093
+ return () => {
1094
+ clearTimeout(handle);
1095
+ ++searchGenRef.current;
1096
+ };
1097
+ }, [query, resultsOpen, searchEnabled, runSearch]);
1098
+
1099
+ useEffect(() => {
1100
+ if (!resultsOpen || shouldUseSheet) return;
1101
+ const onDoc = (event: MouseEvent) => {
1102
+ const root = searchRootRef.current;
1103
+ if (root && event.target instanceof Node && root.contains(event.target)) return;
1104
+ closeSearch(false);
1105
+ };
1106
+ const onKey = (event: globalThis.KeyboardEvent) => {
1107
+ if (event.key === "Escape") closeSearch();
1108
+ };
1109
+ document.addEventListener("mousedown", onDoc);
1110
+ document.addEventListener("keydown", onKey);
1111
+ return () => {
1112
+ document.removeEventListener("mousedown", onDoc);
1113
+ document.removeEventListener("keydown", onKey);
1114
+ };
1115
+ }, [resultsOpen, shouldUseSheet, closeSearch]);
1116
+
679
1117
  const makeHandlers = useCallback(
680
1118
  (currentValue: string, onValueChange: (value: string) => void) => {
681
1119
  onValueChangeRef.current = onValueChange;
@@ -683,64 +1121,329 @@ export function Geolocation({
683
1121
  const coordinates = parseGeoValue(currentValue);
684
1122
  const shape = detectGeoShape(currentValue);
685
1123
  const write = (lat: number, lng: number) => {
1124
+ cancelLocate();
686
1125
  onValueChange(
687
1126
  formatGeoValue({ lat: clampLat(lat), lng: clampLng(lng) }, shape, currentValue),
688
1127
  );
689
1128
  };
690
1129
  return {
691
1130
  coordinates,
692
- onCommitCoordinates: write,
693
- onMapChange: write,
1131
+ onCommitCoordinates: (lat: number, lng: number) => {
1132
+ write(lat, lng);
1133
+ selectedPlaceRef.current = null;
1134
+ queryIsAddressRef.current = true;
1135
+ },
1136
+ onMapChange: (lat: number, lng: number) => {
1137
+ write(lat, lng);
1138
+ selectedPlaceRef.current = null;
1139
+ queryIsAddressRef.current = true;
1140
+ },
694
1141
  onUseMyLocation: () => {
695
1142
  void startLocate();
696
1143
  },
697
1144
  onRetryLocate: () => {
698
1145
  requestPosition();
699
1146
  },
700
- onCloseLocate: () => {
701
- setLocatePhase("idle");
702
- setLocateError(null);
1147
+ onCloseLocate: cancelLocate,
1148
+ onPick: (result: GeocodeResult) => {
1149
+ ++reverseGenRef.current;
1150
+ const place = { ...result, lat: clampLat(result.lat), lng: clampLng(result.lng) };
1151
+ selectedPlaceRef.current = { key: coordinateKey(place), place };
1152
+ write(result.lat, result.lng);
1153
+ // A controlled parent may reject or defer the proposal. Keep its
1154
+ // label as a query/candidate until the bound pair actually matches.
1155
+ queryIsAddressRef.current = true;
1156
+ setQuery(formatPlace(result));
1157
+ closeSearch();
1158
+ setCellOpen(false);
1159
+ setSearchStatus("idle");
1160
+ },
1161
+ onPickPair: (lat: number, lng: number) => {
1162
+ write(lat, lng);
1163
+ selectedPlaceRef.current = null;
1164
+ queryIsAddressRef.current = true;
1165
+ closeSearch();
1166
+ setCellOpen(false);
1167
+ setSearchStatus("idle");
1168
+ },
1169
+ onClear: () => {
1170
+ cancelLocate();
1171
+ ++searchGenRef.current;
1172
+ ++reverseGenRef.current;
1173
+ selectedPlaceRef.current = null;
1174
+ onValueChange("");
1175
+ applyAddress(null, true);
1176
+ setQuery("");
1177
+ setResults([]);
1178
+ setSearchStatus("idle");
703
1179
  },
704
1180
  };
705
1181
  },
706
- [startLocate, requestPosition],
1182
+ [startLocate, requestPosition, cancelLocate, applyAddress, closeSearch],
707
1183
  );
708
1184
 
709
1185
  const mapHeight = getFieldHeight((size ?? knobProps.sizeToken) as SizeTokens, compact ? 4 : 5);
1186
+ const displayError = error || preflightError;
710
1187
 
711
- // Skeleton mirrors map-primary anatomy (no lat/lng row).
712
- if (skeleton) {
1188
+ const renderSearch = (
1189
+ handlers: ReturnType<typeof makeHandlers>,
1190
+ opts: { sheetHeader?: boolean; editingCell?: boolean } = {},
1191
+ ) => {
1192
+ const showClear = Boolean(query) && !disabled && !readOnly;
1193
+ const inputRef = opts.sheetHeader ? sheetSearchInputRef : searchInputRef;
1194
+ const activateNativeSearch = (focusInput = false) => {
1195
+ nativeSearchFocus.activate();
1196
+ if (!searchEnabled || opts.editingCell) return;
1197
+ const nativeInput = inputRef.current as unknown as {
1198
+ isFocused?: () => boolean;
1199
+ focus: () => void;
1200
+ } | null;
1201
+ // Let first focus open the keyboard before mounting the modal. A restored
1202
+ // input is already focused, so a deliberate new press must open directly.
1203
+ if (nativeInput?.isFocused?.()) setResultsOpen(true);
1204
+ else if (focusInput) nativeInput?.focus();
1205
+ };
1206
+ const clearSearch = () => {
1207
+ handlers.onClear();
1208
+ closingSearchRef.current = true;
1209
+ if (isWeb) inputRef.current?.focus({ preventScroll: true });
1210
+ closingSearchRef.current = false;
1211
+ };
713
1212
  return (
714
- <FieldLayout id={id} label={label} size={size} knobProps={knobProps}>
715
- <Skeleton variant="rounded" width="100%" height={mapHeight} />
716
- </FieldLayout>
1213
+ <InputParts size={size || knobProps.sizeToken}>
1214
+ <InputParts.Box
1215
+ disabled={disabled || !searchEnabled}
1216
+ // Keep the square Clear target pressable across the frame's border.
1217
+ style={{ overflow: "visible" }}
1218
+ {...(opts.sheetHeader
1219
+ ? {
1220
+ borderWidth: 0,
1221
+ borderBottomWidth: 1,
1222
+ borderRadius: 0,
1223
+ }
1224
+ : {})}
1225
+ >
1226
+ <InputParts.Section>
1227
+ <InputParts.Icon adornment="leading">
1228
+ <MagnifyingGlassIcon size={16} />
1229
+ </InputParts.Icon>
1230
+ </InputParts.Section>
1231
+ <InputParts.Section>
1232
+ <InputParts.Area
1233
+ ref={inputRef as never}
1234
+ value={query}
1235
+ disabled={disabled || !searchEnabled}
1236
+ placeholder={t("Search address or place")}
1237
+ aria-label={t("Search for a location")}
1238
+ role="combobox"
1239
+ aria-haspopup="listbox"
1240
+ aria-expanded={resultsOpen}
1241
+ aria-controls={listId}
1242
+ aria-autocomplete="list"
1243
+ data-testid="geolocation-search"
1244
+ onChangeText={(text: string) => {
1245
+ if (!isWeb) nativeSearchFocus.activate();
1246
+ ++searchGenRef.current;
1247
+ queryIsAddressRef.current = false;
1248
+ setResults([]);
1249
+ setSearchStatus("idle");
1250
+ setQuery(text);
1251
+ if (searchEnabled) setResultsOpen(true);
1252
+ }}
1253
+ onKeyDown={(event: KeyboardEvent) => {
1254
+ if (event.key === "ArrowDown") {
1255
+ if (searchEnabled) setResultsOpen(true);
1256
+ event.preventDefault();
1257
+ document
1258
+ .getElementById(listId)
1259
+ ?.querySelector<HTMLElement>("[role='option']:not([aria-disabled='true'])")
1260
+ ?.focus();
1261
+ }
1262
+ }}
1263
+ onClick={() => {
1264
+ if (searchEnabled) setResultsOpen(true);
1265
+ }}
1266
+ {...(!isWeb
1267
+ ? {
1268
+ onPressIn: () => activateNativeSearch(),
1269
+ onAccessibilityTap: () => activateNativeSearch(true),
1270
+ onBlur: () => nativeSearchFocus.blur(!!opts.sheetHeader, resultsOpen),
1271
+ }
1272
+ : {})}
1273
+ onFocus={() => {
1274
+ if (
1275
+ searchEnabled &&
1276
+ !opts.editingCell &&
1277
+ !closingSearchRef.current &&
1278
+ (isWeb || nativeSearchFocus.canOpen(!!opts.sheetHeader))
1279
+ )
1280
+ setResultsOpen(true);
1281
+ }}
1282
+ />
1283
+ </InputParts.Section>
1284
+ {showClear ? (
1285
+ <InputParts.Section>
1286
+ <InputParts.Button
1287
+ glyphRing
1288
+ onPress={clearSearch}
1289
+ onKeyDown={(event: KeyboardEvent) => {
1290
+ if (event.key === "Enter" || event.key === " ") {
1291
+ event.preventDefault();
1292
+ event.stopPropagation();
1293
+ clearSearch();
1294
+ }
1295
+ }}
1296
+ aria-label={t("Clear location")}
1297
+ testID="geolocation-search-clear"
1298
+ >
1299
+ <InputParts.Icon>
1300
+ <XIcon size={14} />
1301
+ </InputParts.Icon>
1302
+ </InputParts.Button>
1303
+ </InputParts.Section>
1304
+ ) : null}
1305
+ </InputParts.Box>
1306
+ </InputParts>
717
1307
  );
718
- }
1308
+ };
719
1309
 
720
- // Pre-flight failures surface as the field error; post-preflight errors stay
721
- // inside the locate panel (Retry + Close) and are not mirrored here.
722
- const displayError = error || preflightError;
1310
+ const renderResults = (handlers: ReturnType<typeof makeHandlers>) => (
1311
+ <SearchResultsList
1312
+ listId={listId}
1313
+ status={searchStatus}
1314
+ query={query.trim()}
1315
+ results={results}
1316
+ pair={parseLatLngPair(query)}
1317
+ onLocate={handlers.onUseMyLocation}
1318
+ onPick={handlers.onPick}
1319
+ onPickPair={handlers.onPickPair}
1320
+ locateDisabled={disabled || locatePhase === "locating"}
1321
+ />
1322
+ );
1323
+
1324
+ const renderFormBody = (
1325
+ currentValue: string,
1326
+ onValueChange: (v: string) => void,
1327
+ editingCell = false,
1328
+ resolvedError: string | boolean | undefined = displayError,
1329
+ ): ReactNode => {
1330
+ const handlers = makeHandlers(currentValue, onValueChange);
1331
+ const coordinates = handlers.coordinates;
1332
+ const key = coordinates ? coordinateKey(coordinates) : null;
1333
+ const selected = selectedPlaceRef.current;
1334
+ const displayAddress =
1335
+ (selected?.key === key ? formatPlace(selected.place) : null) ??
1336
+ (address?.key === key ? address.label : null) ??
1337
+ (coordinates ? formatCoordPair(coordinates.lat, coordinates.lng) : null);
1338
+
1339
+ if (inTableCell && !editingCell) {
1340
+ const face = (
1341
+ <CellFace
1342
+ address={displayAddress}
1343
+ coordinates={coordinates}
1344
+ onOpen={!disabled && !readOnly ? () => setCellOpen(true) : undefined}
1345
+ />
1346
+ );
1347
+ if (readOnly || disabled) return face;
1348
+ return (
1349
+ <FloatingPanel
1350
+ open={cellOpen}
1351
+ onOpenChange={setCellOpen}
1352
+ disabled={disabled}
1353
+ fitContent={false}
1354
+ contentPadding="none"
1355
+ trigger={face}
1356
+ >
1357
+ <YStack minWidth={260} maxWidth={420}>
1358
+ {renderFormBody(currentValue, onValueChange, true, resolvedError)}
1359
+ </YStack>
1360
+ </FloatingPanel>
1361
+ );
1362
+ }
1363
+
1364
+ if (readOnly) {
1365
+ return (
1366
+ <YStack gap="$2">
1367
+ <XStack testID="geolocation-readonly-address" alignItems="center" gap="$2" minHeight={25}>
1368
+ <MapPinIcon size={14} color={glyphColor} />
1369
+ <Text fontSize="$4" lineHeight={25} color="$color12">
1370
+ {displayAddress ?? t("No location set")}
1371
+ </Text>
1372
+ </XStack>
1373
+ <GeoMapSurface
1374
+ id={id}
1375
+ coordinates={coordinates}
1376
+ onCommitCoordinates={handlers.onCommitCoordinates}
1377
+ onUseMyLocation={handlers.onUseMyLocation}
1378
+ onRetryLocate={handlers.onRetryLocate}
1379
+ onCloseLocate={handlers.onCloseLocate}
1380
+ onMapChange={handlers.onMapChange}
1381
+ disabled={!!disabled}
1382
+ readOnly
1383
+ locatePhase={locatePhase}
1384
+ locateError={locateError}
1385
+ knobProps={knobProps}
1386
+ locationIcon={resolvedLocationIcon}
1387
+ expandIcon={expandIcon}
1388
+ renderMap={renderMap}
1389
+ required={required}
1390
+ hasError={!!resolvedError}
1391
+ mapHeight={mapHeight}
1392
+ />
1393
+ </YStack>
1394
+ );
1395
+ }
1396
+
1397
+ const resultsPanel =
1398
+ resultsOpen && searchEnabled ? (
1399
+ shouldUseSheet ? (
1400
+ <FloatingPanel
1401
+ open={resultsOpen}
1402
+ onOpenChange={(open) => (open ? setResultsOpen(true) : closeSearch())}
1403
+ onNativeShow={() => sheetSearchInputRef.current?.focus()}
1404
+ skipReturnFocusRef={skipResultsReturnFocusRef}
1405
+ sheet
1406
+ sheetFill
1407
+ scrollable
1408
+ contentPadding="none"
1409
+ header={renderSearch(handlers, { sheetHeader: true })}
1410
+ trigger={<View testID="geolocation-sheet-trigger" height={1} width="100%" />}
1411
+ >
1412
+ <YStack testID="geolocation-results-sheet">{renderResults(handlers)}</YStack>
1413
+ </FloatingPanel>
1414
+ ) : (
1415
+ <YStack
1416
+ position="absolute"
1417
+ top="100%"
1418
+ left={0}
1419
+ right={0}
1420
+ zIndex={zIndex.dropdown}
1421
+ backgroundColor="$color1"
1422
+ borderWidth={1}
1423
+ borderColor={formInputColors.border.base}
1424
+ borderRadius={knobProps.borderRadius.borderRadius}
1425
+ overflow="hidden"
1426
+ style={{ boxShadow: RESULTS_PANEL_SHADOW }}
1427
+ >
1428
+ <ScrollView maxHeight={mapHeight * 1.5}>{renderResults(handlers)}</ScrollView>
1429
+ </YStack>
1430
+ )
1431
+ ) : null;
723
1432
 
724
- if (!resolvedForm || !name) {
725
- const handlers = makeHandlers(value || "", (v) => onChange?.(v));
726
1433
  return (
727
- <FieldLayout
728
- id={id}
729
- label={label}
730
- labelProps={labelProps}
731
- error={displayError}
732
- helperText={helperText}
733
- required={required}
734
- size={size}
735
- knobProps={knobProps}
736
- onBlur={onBlur}
737
- // LC-40: FieldLayout owns the dimWhole assembly dim (keepLabel pins
738
- // the sibling label/helper readable) — it needs the disabled state.
739
- disabled={disabled}
740
- >
1434
+ <YStack gap="$2">
1435
+ <YStack
1436
+ ref={searchRootRef as never}
1437
+ position="relative"
1438
+ testID="geolocation-search-anchor"
1439
+ data-sheet={shouldUseSheet ? "true" : "false"}
1440
+ >
1441
+ {renderSearch(handlers, { editingCell })}
1442
+ {resultsPanel}
1443
+ </YStack>
741
1444
  <GeoMapSurface
742
1445
  id={id}
743
- coordinates={handlers.coordinates}
1446
+ coordinates={coordinates}
744
1447
  onCommitCoordinates={handlers.onCommitCoordinates}
745
1448
  onUseMyLocation={handlers.onUseMyLocation}
746
1449
  onRetryLocate={handlers.onRetryLocate}
@@ -751,17 +1454,61 @@ export function Geolocation({
751
1454
  locatePhase={locatePhase}
752
1455
  locateError={locateError}
753
1456
  knobProps={knobProps}
754
- locationIcon={locationIcon}
1457
+ locationIcon={resolvedLocationIcon}
755
1458
  expandIcon={expandIcon}
756
1459
  renderMap={renderMap}
757
1460
  required={required}
758
- hasError={!!displayError}
1461
+ hasError={!!resolvedError}
759
1462
  mapHeight={mapHeight}
760
1463
  />
1464
+ </YStack>
1465
+ );
1466
+ };
1467
+
1468
+ if (skeleton) {
1469
+ return (
1470
+ <FieldLayout id={id} label={label} size={size} knobProps={knobProps}>
1471
+ <YStack gap="$2">
1472
+ <Skeleton variant="rounded" width="100%" height={44} />
1473
+ <Skeleton variant="rounded" width="100%" height={mapHeight} />
1474
+ </YStack>
761
1475
  </FieldLayout>
762
1476
  );
763
1477
  }
764
1478
 
1479
+ const wrap = (
1480
+ currentValue: string,
1481
+ onValueChange: (v: string) => void,
1482
+ resolvedError?: string | boolean,
1483
+ fieldBlur?: (...args: any[]) => void,
1484
+ ) => {
1485
+ const coordinates = parseGeoValue(currentValue);
1486
+ const taughtHelper =
1487
+ helperText ??
1488
+ (!coordinates && !readOnly ? t("Search, tap the map, or use your location.") : undefined);
1489
+ return (
1490
+ <FieldLayout
1491
+ id={id}
1492
+ label={label}
1493
+ labelProps={labelProps}
1494
+ error={resolvedError ?? displayError}
1495
+ helperText={inTableCell ? undefined : taughtHelper}
1496
+ required={required}
1497
+ size={size}
1498
+ knobProps={knobProps}
1499
+ onBlur={fieldBlur ?? onBlur}
1500
+ disabled={disabled}
1501
+ >
1502
+ <GeolocationValueSync value={currentValue} runReverse={runReverse} />
1503
+ {renderFormBody(currentValue, onValueChange, false, resolvedError)}
1504
+ </FieldLayout>
1505
+ );
1506
+ };
1507
+
1508
+ if (!resolvedForm || !name) {
1509
+ return wrap(value || "", (v) => onChange?.(v));
1510
+ }
1511
+
765
1512
  return (
766
1513
  <Field
767
1514
  form={resolvedForm}
@@ -771,47 +1518,32 @@ export function Geolocation({
771
1518
  >
772
1519
  {(field) => {
773
1520
  const resolvedError = getFieldError(field, displayError);
774
- const handlers = makeHandlers((field.state.value as string) || "", (v) => {
775
- field.handleChange(v as any);
776
- onChange?.(v);
777
- });
778
- return (
779
- <FieldLayout
780
- id={id}
781
- label={label}
782
- labelProps={labelProps}
783
- error={resolvedError}
784
- helperText={helperText}
785
- required={required}
786
- size={size}
787
- knobProps={knobProps}
788
- onBlur={mergeFieldHandler(field, "handleBlur", onBlur)}
789
- // LC-40: see standalone branch — FieldLayout carries dimWhole.
790
- disabled={disabled}
791
- >
792
- <GeoMapSurface
793
- id={id}
794
- coordinates={handlers.coordinates}
795
- onCommitCoordinates={handlers.onCommitCoordinates}
796
- onUseMyLocation={handlers.onUseMyLocation}
797
- onRetryLocate={handlers.onRetryLocate}
798
- onCloseLocate={handlers.onCloseLocate}
799
- onMapChange={handlers.onMapChange}
800
- disabled={!!disabled}
801
- readOnly={!!readOnly}
802
- locatePhase={locatePhase}
803
- locateError={locateError}
804
- knobProps={knobProps}
805
- locationIcon={locationIcon}
806
- expandIcon={expandIcon}
807
- renderMap={renderMap}
808
- required={required}
809
- hasError={!!resolvedError}
810
- mapHeight={mapHeight}
811
- />
812
- </FieldLayout>
1521
+ return wrap(
1522
+ (field.state.value as string) || "",
1523
+ (v) => {
1524
+ field.handleChange(v as never);
1525
+ onChange?.(v);
1526
+ },
1527
+ resolvedError,
1528
+ mergeFieldHandler(field, "handleBlur", onBlur),
813
1529
  );
814
1530
  }}
815
1531
  </Field>
816
1532
  );
817
1533
  }
1534
+
1535
+ function GeolocationValueSync({
1536
+ value,
1537
+ runReverse,
1538
+ }: {
1539
+ value: string;
1540
+ runReverse: (lat?: number, lng?: number) => void;
1541
+ }) {
1542
+ const coords = parseGeoValue(value);
1543
+ const key = coords ? coordinateKey(coords) : "";
1544
+ useEffect(() => {
1545
+ const next = parseGeoValue(value);
1546
+ void runReverse(next?.lat, next?.lng);
1547
+ }, [key, value, runReverse]);
1548
+ return null;
1549
+ }