react-native-platform-components 1.4.0 → 1.5.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 (598) hide show
  1. package/PlatformComponents.podspec +1 -1
  2. package/README.md +33 -31
  3. package/android/src/main/java/com/platformcomponents/PCButtonGroupView.kt +175 -9
  4. package/android/src/main/java/com/platformcomponents/PCButtonGroupViewManager.kt +52 -2
  5. package/android/src/main/java/com/platformcomponents/PCButtonSupport.kt +44 -12
  6. package/android/src/main/java/com/platformcomponents/PCButtonView.kt +187 -5
  7. package/android/src/main/java/com/platformcomponents/PCButtonViewManager.kt +85 -7
  8. package/android/src/main/java/com/platformcomponents/PCContextMenuView.kt +41 -151
  9. package/android/src/main/java/com/platformcomponents/PCContextMenuViewManager.kt +7 -63
  10. package/android/src/main/java/com/platformcomponents/PCDatePickerView.kt +310 -115
  11. package/android/src/main/java/com/platformcomponents/PCDatePickerViewManager.kt +23 -3
  12. package/android/src/main/java/com/platformcomponents/PCExpressive.kt +6 -1
  13. package/android/src/main/java/com/platformcomponents/PCFloatingActionButtonView.kt +485 -0
  14. package/android/src/main/java/com/platformcomponents/PCFloatingActionButtonViewManager.kt +109 -0
  15. package/android/src/main/java/com/platformcomponents/PCFloatingToolbarView.kt +132 -0
  16. package/android/src/main/java/com/platformcomponents/PCFloatingToolbarViewManager.kt +8 -0
  17. package/android/src/main/java/com/platformcomponents/PCHaptics.kt +46 -0
  18. package/android/src/main/java/com/platformcomponents/PCImageLoader.kt +42 -7
  19. package/android/src/main/java/com/platformcomponents/PCLiquidGlassContainerView.kt +32 -0
  20. package/android/src/main/java/com/platformcomponents/PCLiquidGlassContainerViewManager.kt +31 -0
  21. package/android/src/main/java/com/platformcomponents/PCLiquidGlassView.kt +32 -6
  22. package/android/src/main/java/com/platformcomponents/PCLiquidGlassViewManager.kt +4 -0
  23. package/android/src/main/java/com/platformcomponents/PCMenuSupport.kt +280 -0
  24. package/android/src/main/java/com/platformcomponents/PCNavigationBarSupport.kt +332 -0
  25. package/android/src/main/java/com/platformcomponents/PCNavigationRailView.kt +448 -0
  26. package/android/src/main/java/com/platformcomponents/PCNavigationRailViewManager.kt +176 -0
  27. package/android/src/main/java/com/platformcomponents/PCSegmentedControlView.kt +24 -14
  28. package/android/src/main/java/com/platformcomponents/PCSegmentedControlViewManager.kt +14 -2
  29. package/android/src/main/java/com/platformcomponents/PCSelectionMenuView.kt +371 -39
  30. package/android/src/main/java/com/platformcomponents/PCSelectionMenuViewManager.kt +18 -4
  31. package/android/src/main/java/com/platformcomponents/PCTabBarView.kt +397 -0
  32. package/android/src/main/java/com/platformcomponents/PCTabBarViewManager.kt +182 -0
  33. package/android/src/main/java/com/platformcomponents/PCTextFieldView.kt +415 -40
  34. package/android/src/main/java/com/platformcomponents/PCTextFieldViewManager.kt +109 -3
  35. package/android/src/main/java/com/platformcomponents/PCThemeSupport.kt +37 -0
  36. package/android/src/main/java/com/platformcomponents/PlatformComponentsPackage.kt +4 -0
  37. package/android/src/main/res/drawable/pc_ic_search.xml +12 -0
  38. package/android/src/main/res/values/ids.xml +5 -0
  39. package/android/src/main/res/values/styles.xml +24 -0
  40. package/ios/PCButton.mm +122 -7
  41. package/ios/PCButtonGroup.mm +68 -1
  42. package/ios/PCButtonGroupView.swift +225 -6
  43. package/ios/PCButtonSupport.swift +181 -10
  44. package/ios/PCButtonView.swift +297 -14
  45. package/ios/PCConstants.swift +23 -2
  46. package/ios/PCContextMenu.mm +28 -110
  47. package/ios/PCContextMenu.swift +106 -167
  48. package/ios/PCDatePicker.mm +21 -9
  49. package/ios/PCDatePickerView.swift +94 -20
  50. package/ios/PCFloatingActionButton.h +10 -0
  51. package/ios/PCFloatingActionButton.mm +177 -0
  52. package/ios/PCFloatingActionButtonView.swift +426 -0
  53. package/ios/PCFloatingToolbar.mm +27 -0
  54. package/ios/PCFloatingToolbarView.swift +241 -0
  55. package/ios/PCHaptics.swift +91 -0
  56. package/ios/PCImageLoader.swift +76 -12
  57. package/ios/PCLiquidGlass.mm +36 -0
  58. package/ios/PCLiquidGlass.swift +221 -114
  59. package/ios/PCLiquidGlassContainer.h +10 -0
  60. package/ios/PCLiquidGlassContainer.mm +84 -0
  61. package/ios/PCLiquidGlassContainer.swift +69 -0
  62. package/ios/PCMenuItems.h +76 -0
  63. package/ios/PCMenuSupport.swift +350 -0
  64. package/ios/PCNavigationRail.h +10 -0
  65. package/ios/PCNavigationRail.mm +270 -0
  66. package/ios/PCNavigationRailView.swift +415 -0
  67. package/ios/PCSegmentedControl.mm +17 -2
  68. package/ios/PCSegmentedControl.swift +57 -7
  69. package/ios/PCSelectionMenu.mm +36 -8
  70. package/ios/PCSelectionMenu.swift +224 -48
  71. package/ios/PCTabBar.h +10 -0
  72. package/ios/PCTabBar.mm +285 -0
  73. package/ios/PCTabBarAccessory.h +10 -0
  74. package/ios/PCTabBarAccessory.mm +93 -0
  75. package/ios/PCTabBarAccessoryView.swift +96 -0
  76. package/ios/PCTabBarView.swift +671 -0
  77. package/ios/PCTextField.mm +159 -4
  78. package/ios/PCTextFieldView.swift +603 -57
  79. package/lib/commonjs/Button.js +73 -1
  80. package/lib/commonjs/Button.js.map +1 -1
  81. package/lib/commonjs/ButtonGroup.js +13 -3
  82. package/lib/commonjs/ButtonGroup.js.map +1 -1
  83. package/lib/commonjs/ButtonGroupNativeComponent.ts +74 -4
  84. package/lib/commonjs/ButtonNativeComponent.ts +119 -3
  85. package/lib/commonjs/ContextMenu.js +9 -48
  86. package/lib/commonjs/ContextMenu.js.map +1 -1
  87. package/lib/commonjs/ContextMenuNativeComponent.ts +47 -49
  88. package/lib/commonjs/DatePicker.js +98 -2
  89. package/lib/commonjs/DatePicker.js.map +1 -1
  90. package/lib/commonjs/DatePickerNativeComponent.ts +19 -2
  91. package/lib/commonjs/FloatingActionButton.js +60 -0
  92. package/lib/commonjs/FloatingActionButton.js.map +1 -0
  93. package/lib/commonjs/FloatingActionButtonNativeComponent.ts +58 -0
  94. package/lib/commonjs/FloatingToolbar.js +9 -1
  95. package/lib/commonjs/FloatingToolbar.js.map +1 -1
  96. package/lib/commonjs/FloatingToolbarNativeComponent.ts +16 -0
  97. package/lib/commonjs/LiquidGlass.js +7 -1
  98. package/lib/commonjs/LiquidGlass.js.map +1 -1
  99. package/lib/commonjs/LiquidGlassContainer.js +32 -0
  100. package/lib/commonjs/LiquidGlassContainer.js.map +1 -0
  101. package/lib/commonjs/LiquidGlassContainerNativeComponent.ts +17 -0
  102. package/lib/commonjs/LiquidGlassNativeComponent.ts +8 -0
  103. package/lib/commonjs/NavigationRail.js +104 -0
  104. package/lib/commonjs/NavigationRail.js.map +1 -0
  105. package/lib/commonjs/NavigationRailNativeComponent.ts +107 -0
  106. package/lib/commonjs/PlatformComponentsProvider.js +18 -0
  107. package/lib/commonjs/PlatformComponentsProvider.js.map +1 -0
  108. package/lib/commonjs/SegmentedControl.js +7 -0
  109. package/lib/commonjs/SegmentedControl.js.map +1 -1
  110. package/lib/commonjs/SegmentedControlNativeComponent.ts +11 -0
  111. package/lib/commonjs/SelectionMenu.js +27 -3
  112. package/lib/commonjs/SelectionMenu.js.map +1 -1
  113. package/lib/commonjs/SelectionMenuNativeComponent.ts +26 -4
  114. package/lib/commonjs/SplitButton.js +98 -0
  115. package/lib/commonjs/SplitButton.js.map +1 -0
  116. package/lib/commonjs/TabBar.js +203 -0
  117. package/lib/commonjs/TabBar.js.map +1 -0
  118. package/lib/commonjs/TabBarAccessoryNativeComponent.ts +19 -0
  119. package/lib/commonjs/TabBarNativeComponent.ts +145 -0
  120. package/lib/commonjs/TextField.js +171 -8
  121. package/lib/commonjs/TextField.js.map +1 -1
  122. package/lib/commonjs/TextFieldNativeComponent.ts +110 -4
  123. package/lib/commonjs/focusRegistry.js +2 -1
  124. package/lib/commonjs/focusRegistry.js.map +1 -1
  125. package/lib/commonjs/haptics.js +2 -0
  126. package/lib/commonjs/haptics.js.map +1 -0
  127. package/lib/commonjs/icons.js +15 -1
  128. package/lib/commonjs/icons.js.map +1 -1
  129. package/lib/commonjs/index.js +77 -0
  130. package/lib/commonjs/index.js.map +1 -1
  131. package/lib/commonjs/index.web.js +156 -92
  132. package/lib/commonjs/index.web.js.map +1 -1
  133. package/lib/commonjs/jest.js +910 -0
  134. package/lib/commonjs/jest.js.map +1 -0
  135. package/lib/commonjs/menuItems.js +68 -0
  136. package/lib/commonjs/menuItems.js.map +1 -0
  137. package/lib/commonjs/selectionIdentifiers.js +18 -0
  138. package/lib/commonjs/selectionIdentifiers.js.map +1 -0
  139. package/lib/commonjs/submitBehavior.js +19 -0
  140. package/lib/commonjs/submitBehavior.js.map +1 -0
  141. package/lib/commonjs/tabItems.js +49 -0
  142. package/lib/commonjs/tabItems.js.map +1 -0
  143. package/lib/commonjs/web/Button.js +170 -0
  144. package/lib/commonjs/web/Button.js.map +1 -0
  145. package/lib/commonjs/web/ButtonGroup.js +119 -0
  146. package/lib/commonjs/web/ButtonGroup.js.map +1 -0
  147. package/lib/commonjs/web/DatePicker.js +168 -0
  148. package/lib/commonjs/web/DatePicker.js.map +1 -0
  149. package/lib/commonjs/web/DateRangePicker.js +137 -0
  150. package/lib/commonjs/web/DateRangePicker.js.map +1 -0
  151. package/lib/commonjs/web/Dialog.js +54 -0
  152. package/lib/commonjs/web/Dialog.js.map +1 -0
  153. package/lib/commonjs/web/FloatingActionButton.js +98 -0
  154. package/lib/commonjs/web/FloatingActionButton.js.map +1 -0
  155. package/lib/commonjs/web/Icon.js +34 -0
  156. package/lib/commonjs/web/Icon.js.map +1 -0
  157. package/lib/commonjs/web/NativeTheme.js +28 -0
  158. package/lib/commonjs/web/NativeTheme.js.map +1 -0
  159. package/lib/commonjs/web/NavigationRail.js +139 -0
  160. package/lib/commonjs/web/NavigationRail.js.map +1 -0
  161. package/lib/commonjs/web/PlatformComponentsProvider.js +51 -0
  162. package/lib/commonjs/web/PlatformComponentsProvider.js.map +1 -0
  163. package/lib/commonjs/web/SegmentedControl.js +117 -0
  164. package/lib/commonjs/web/SegmentedControl.js.map +1 -0
  165. package/lib/commonjs/web/SelectionMenu.js +142 -0
  166. package/lib/commonjs/web/SelectionMenu.js.map +1 -0
  167. package/lib/commonjs/web/SplitButton.js +35 -0
  168. package/lib/commonjs/web/SplitButton.js.map +1 -0
  169. package/lib/commonjs/web/TabBar.js +131 -0
  170. package/lib/commonjs/web/TabBar.js.map +1 -0
  171. package/lib/commonjs/web/TextField.js +322 -0
  172. package/lib/commonjs/web/TextField.js.map +1 -0
  173. package/lib/commonjs/web/components.js +36 -0
  174. package/lib/commonjs/web/components.js.map +1 -0
  175. package/lib/commonjs/web/containers.js +121 -0
  176. package/lib/commonjs/web/containers.js.map +1 -0
  177. package/lib/commonjs/web/dateInput.js +116 -0
  178. package/lib/commonjs/web/dateInput.js.map +1 -0
  179. package/lib/commonjs/web/shared.js +192 -0
  180. package/lib/commonjs/web/shared.js.map +1 -0
  181. package/lib/commonjs/webComponents.js +6 -0
  182. package/lib/commonjs/webComponents.js.map +1 -0
  183. package/lib/module/Button.js +74 -2
  184. package/lib/module/Button.js.map +1 -1
  185. package/lib/module/ButtonGroup.js +13 -3
  186. package/lib/module/ButtonGroup.js.map +1 -1
  187. package/lib/module/ButtonGroupNativeComponent.ts +74 -4
  188. package/lib/module/ButtonNativeComponent.ts +119 -3
  189. package/lib/module/ContextMenu.js +9 -48
  190. package/lib/module/ContextMenu.js.map +1 -1
  191. package/lib/module/ContextMenuNativeComponent.ts +47 -49
  192. package/lib/module/DatePicker.js +98 -4
  193. package/lib/module/DatePicker.js.map +1 -1
  194. package/lib/module/DatePickerNativeComponent.ts +19 -2
  195. package/lib/module/FloatingActionButton.js +53 -0
  196. package/lib/module/FloatingActionButton.js.map +1 -0
  197. package/lib/module/FloatingActionButtonNativeComponent.ts +58 -0
  198. package/lib/module/FloatingToolbar.js +9 -1
  199. package/lib/module/FloatingToolbar.js.map +1 -1
  200. package/lib/module/FloatingToolbarNativeComponent.ts +16 -0
  201. package/lib/module/LiquidGlass.js +7 -1
  202. package/lib/module/LiquidGlass.js.map +1 -1
  203. package/lib/module/LiquidGlassContainer.js +26 -0
  204. package/lib/module/LiquidGlassContainer.js.map +1 -0
  205. package/lib/module/LiquidGlassContainerNativeComponent.ts +17 -0
  206. package/lib/module/LiquidGlassNativeComponent.ts +8 -0
  207. package/lib/module/NavigationRail.js +99 -0
  208. package/lib/module/NavigationRail.js.map +1 -0
  209. package/lib/module/NavigationRailNativeComponent.ts +107 -0
  210. package/lib/module/PlatformComponentsProvider.js +13 -0
  211. package/lib/module/PlatformComponentsProvider.js.map +1 -0
  212. package/lib/module/SegmentedControl.js +9 -1
  213. package/lib/module/SegmentedControl.js.map +1 -1
  214. package/lib/module/SegmentedControlNativeComponent.ts +11 -0
  215. package/lib/module/SelectionMenu.js +27 -3
  216. package/lib/module/SelectionMenu.js.map +1 -1
  217. package/lib/module/SelectionMenuNativeComponent.ts +26 -4
  218. package/lib/module/SplitButton.js +91 -0
  219. package/lib/module/SplitButton.js.map +1 -0
  220. package/lib/module/TabBar.js +196 -0
  221. package/lib/module/TabBar.js.map +1 -0
  222. package/lib/module/TabBarAccessoryNativeComponent.ts +19 -0
  223. package/lib/module/TabBarNativeComponent.ts +145 -0
  224. package/lib/module/TextField.js +172 -9
  225. package/lib/module/TextField.js.map +1 -1
  226. package/lib/module/TextFieldNativeComponent.ts +110 -4
  227. package/lib/module/focusRegistry.js +1 -0
  228. package/lib/module/focusRegistry.js.map +1 -1
  229. package/lib/module/haptics.js +2 -0
  230. package/lib/module/haptics.js.map +1 -0
  231. package/lib/module/icons.js +15 -1
  232. package/lib/module/icons.js.map +1 -1
  233. package/lib/module/index.js +7 -0
  234. package/lib/module/index.js.map +1 -1
  235. package/lib/module/index.web.js +12 -87
  236. package/lib/module/index.web.js.map +1 -1
  237. package/lib/module/jest.js +840 -0
  238. package/lib/module/jest.js.map +1 -0
  239. package/lib/module/menuItems.js +65 -0
  240. package/lib/module/menuItems.js.map +1 -0
  241. package/lib/module/selectionIdentifiers.js +14 -0
  242. package/lib/module/selectionIdentifiers.js.map +1 -0
  243. package/lib/module/submitBehavior.js +15 -0
  244. package/lib/module/submitBehavior.js.map +1 -0
  245. package/lib/module/tabItems.js +45 -0
  246. package/lib/module/tabItems.js.map +1 -0
  247. package/lib/module/web/Button.js +164 -0
  248. package/lib/module/web/Button.js.map +1 -0
  249. package/lib/module/web/ButtonGroup.js +113 -0
  250. package/lib/module/web/ButtonGroup.js.map +1 -0
  251. package/lib/module/web/DatePicker.js +162 -0
  252. package/lib/module/web/DatePicker.js.map +1 -0
  253. package/lib/module/web/DateRangePicker.js +131 -0
  254. package/lib/module/web/DateRangePicker.js.map +1 -0
  255. package/lib/module/web/Dialog.js +48 -0
  256. package/lib/module/web/Dialog.js.map +1 -0
  257. package/lib/module/web/FloatingActionButton.js +94 -0
  258. package/lib/module/web/FloatingActionButton.js.map +1 -0
  259. package/lib/module/web/Icon.js +29 -0
  260. package/lib/module/web/Icon.js.map +1 -0
  261. package/lib/module/web/NativeTheme.js +23 -0
  262. package/lib/module/web/NativeTheme.js.map +1 -0
  263. package/lib/module/web/NavigationRail.js +133 -0
  264. package/lib/module/web/NavigationRail.js.map +1 -0
  265. package/lib/module/web/PlatformComponentsProvider.js +44 -0
  266. package/lib/module/web/PlatformComponentsProvider.js.map +1 -0
  267. package/lib/module/web/SegmentedControl.js +112 -0
  268. package/lib/module/web/SegmentedControl.js.map +1 -0
  269. package/lib/module/web/SelectionMenu.js +137 -0
  270. package/lib/module/web/SelectionMenu.js.map +1 -0
  271. package/lib/module/web/SplitButton.js +30 -0
  272. package/lib/module/web/SplitButton.js.map +1 -0
  273. package/lib/module/web/TabBar.js +126 -0
  274. package/lib/module/web/TabBar.js.map +1 -0
  275. package/lib/module/web/TextField.js +316 -0
  276. package/lib/module/web/TextField.js.map +1 -0
  277. package/lib/module/web/components.js +33 -0
  278. package/lib/module/web/components.js.map +1 -0
  279. package/lib/module/web/containers.js +112 -0
  280. package/lib/module/web/containers.js.map +1 -0
  281. package/lib/module/web/dateInput.js +109 -0
  282. package/lib/module/web/dateInput.js.map +1 -0
  283. package/lib/module/web/shared.js +179 -0
  284. package/lib/module/web/shared.js.map +1 -0
  285. package/lib/module/webComponents.js +4 -0
  286. package/lib/module/webComponents.js.map +1 -0
  287. package/lib/typescript/commonjs/docs/sidebars.d.ts.map +1 -1
  288. package/lib/typescript/commonjs/src/Button.d.ts +111 -3
  289. package/lib/typescript/commonjs/src/Button.d.ts.map +1 -1
  290. package/lib/typescript/commonjs/src/ButtonGroup.d.ts +21 -6
  291. package/lib/typescript/commonjs/src/ButtonGroup.d.ts.map +1 -1
  292. package/lib/typescript/commonjs/src/ButtonGroupNativeComponent.d.ts +64 -3
  293. package/lib/typescript/commonjs/src/ButtonGroupNativeComponent.d.ts.map +1 -1
  294. package/lib/typescript/commonjs/src/ButtonNativeComponent.d.ts +94 -3
  295. package/lib/typescript/commonjs/src/ButtonNativeComponent.d.ts.map +1 -1
  296. package/lib/typescript/commonjs/src/ContextMenu.d.ts +20 -33
  297. package/lib/typescript/commonjs/src/ContextMenu.d.ts.map +1 -1
  298. package/lib/typescript/commonjs/src/ContextMenuNativeComponent.d.ts +45 -47
  299. package/lib/typescript/commonjs/src/ContextMenuNativeComponent.d.ts.map +1 -1
  300. package/lib/typescript/commonjs/src/DatePicker.d.ts +74 -1
  301. package/lib/typescript/commonjs/src/DatePicker.d.ts.map +1 -1
  302. package/lib/typescript/commonjs/src/DatePickerNativeComponent.d.ts +16 -1
  303. package/lib/typescript/commonjs/src/DatePickerNativeComponent.d.ts.map +1 -1
  304. package/lib/typescript/commonjs/src/FloatingActionButton.d.ts +67 -0
  305. package/lib/typescript/commonjs/src/FloatingActionButton.d.ts.map +1 -0
  306. package/lib/typescript/commonjs/src/FloatingActionButtonNativeComponent.d.ts +43 -0
  307. package/lib/typescript/commonjs/src/FloatingActionButtonNativeComponent.d.ts.map +1 -0
  308. package/lib/typescript/commonjs/src/FloatingToolbar.d.ts +30 -0
  309. package/lib/typescript/commonjs/src/FloatingToolbar.d.ts.map +1 -1
  310. package/lib/typescript/commonjs/src/FloatingToolbarNativeComponent.d.ts +12 -0
  311. package/lib/typescript/commonjs/src/FloatingToolbarNativeComponent.d.ts.map +1 -1
  312. package/lib/typescript/commonjs/src/LiquidGlass.d.ts +17 -0
  313. package/lib/typescript/commonjs/src/LiquidGlass.d.ts.map +1 -1
  314. package/lib/typescript/commonjs/src/LiquidGlassContainer.d.ts +27 -0
  315. package/lib/typescript/commonjs/src/LiquidGlassContainer.d.ts.map +1 -0
  316. package/lib/typescript/commonjs/src/LiquidGlassContainerNativeComponent.d.ts +13 -0
  317. package/lib/typescript/commonjs/src/LiquidGlassContainerNativeComponent.d.ts.map +1 -0
  318. package/lib/typescript/commonjs/src/LiquidGlassNativeComponent.d.ts +7 -0
  319. package/lib/typescript/commonjs/src/LiquidGlassNativeComponent.d.ts.map +1 -1
  320. package/lib/typescript/commonjs/src/NavigationRail.d.ts +86 -0
  321. package/lib/typescript/commonjs/src/NavigationRail.d.ts.map +1 -0
  322. package/lib/typescript/commonjs/src/NavigationRailNativeComponent.d.ts +81 -0
  323. package/lib/typescript/commonjs/src/NavigationRailNativeComponent.d.ts.map +1 -0
  324. package/lib/typescript/commonjs/src/PlatformComponentsProvider.d.ts +6 -0
  325. package/lib/typescript/commonjs/src/PlatformComponentsProvider.d.ts.map +1 -0
  326. package/lib/typescript/commonjs/src/SegmentedControl.d.ts +18 -1
  327. package/lib/typescript/commonjs/src/SegmentedControl.d.ts.map +1 -1
  328. package/lib/typescript/commonjs/src/SegmentedControlNativeComponent.d.ts +9 -0
  329. package/lib/typescript/commonjs/src/SegmentedControlNativeComponent.d.ts.map +1 -1
  330. package/lib/typescript/commonjs/src/SelectionMenu.d.ts +44 -2
  331. package/lib/typescript/commonjs/src/SelectionMenu.d.ts.map +1 -1
  332. package/lib/typescript/commonjs/src/SelectionMenuNativeComponent.d.ts +20 -4
  333. package/lib/typescript/commonjs/src/SelectionMenuNativeComponent.d.ts.map +1 -1
  334. package/lib/typescript/commonjs/src/SplitButton.d.ts +83 -0
  335. package/lib/typescript/commonjs/src/SplitButton.d.ts.map +1 -0
  336. package/lib/typescript/commonjs/src/TabBar.d.ts +144 -0
  337. package/lib/typescript/commonjs/src/TabBar.d.ts.map +1 -0
  338. package/lib/typescript/commonjs/src/TabBarAccessoryNativeComponent.d.ts +13 -0
  339. package/lib/typescript/commonjs/src/TabBarAccessoryNativeComponent.d.ts.map +1 -0
  340. package/lib/typescript/commonjs/src/TabBarNativeComponent.d.ts +110 -0
  341. package/lib/typescript/commonjs/src/TabBarNativeComponent.d.ts.map +1 -0
  342. package/lib/typescript/commonjs/src/TextField.d.ts +166 -8
  343. package/lib/typescript/commonjs/src/TextField.d.ts.map +1 -1
  344. package/lib/typescript/commonjs/src/TextFieldNativeComponent.d.ts +81 -3
  345. package/lib/typescript/commonjs/src/TextFieldNativeComponent.d.ts.map +1 -1
  346. package/lib/typescript/commonjs/src/focusRegistry.d.ts.map +1 -1
  347. package/lib/typescript/commonjs/src/haptics.d.ts +20 -0
  348. package/lib/typescript/commonjs/src/haptics.d.ts.map +1 -0
  349. package/lib/typescript/commonjs/src/icons.d.ts +7 -1
  350. package/lib/typescript/commonjs/src/icons.d.ts.map +1 -1
  351. package/lib/typescript/commonjs/src/index.d.ts +7 -0
  352. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  353. package/lib/typescript/commonjs/src/index.web.d.ts +25 -254
  354. package/lib/typescript/commonjs/src/index.web.d.ts.map +1 -1
  355. package/lib/typescript/commonjs/src/jest.d.ts +139 -0
  356. package/lib/typescript/commonjs/src/jest.d.ts.map +1 -0
  357. package/lib/typescript/commonjs/src/menuItems.d.ts +100 -0
  358. package/lib/typescript/commonjs/src/menuItems.d.ts.map +1 -0
  359. package/lib/typescript/commonjs/src/selectionIdentifiers.d.ts +3 -0
  360. package/lib/typescript/commonjs/src/selectionIdentifiers.d.ts.map +1 -0
  361. package/lib/typescript/commonjs/src/submitBehavior.d.ts +9 -0
  362. package/lib/typescript/commonjs/src/submitBehavior.d.ts.map +1 -0
  363. package/lib/typescript/commonjs/src/tabItems.d.ts +55 -0
  364. package/lib/typescript/commonjs/src/tabItems.d.ts.map +1 -0
  365. package/lib/typescript/commonjs/src/web/Button.d.ts +9 -0
  366. package/lib/typescript/commonjs/src/web/Button.d.ts.map +1 -0
  367. package/lib/typescript/commonjs/src/web/ButtonGroup.d.ts +11 -0
  368. package/lib/typescript/commonjs/src/web/ButtonGroup.d.ts.map +1 -0
  369. package/lib/typescript/commonjs/src/web/DatePicker.d.ts +14 -0
  370. package/lib/typescript/commonjs/src/web/DatePicker.d.ts.map +1 -0
  371. package/lib/typescript/commonjs/src/web/DateRangePicker.d.ts +11 -0
  372. package/lib/typescript/commonjs/src/web/DateRangePicker.d.ts.map +1 -0
  373. package/lib/typescript/commonjs/src/web/Dialog.d.ts +12 -0
  374. package/lib/typescript/commonjs/src/web/Dialog.d.ts.map +1 -0
  375. package/lib/typescript/commonjs/src/web/FloatingActionButton.d.ts +8 -0
  376. package/lib/typescript/commonjs/src/web/FloatingActionButton.d.ts.map +1 -0
  377. package/lib/typescript/commonjs/src/web/Icon.d.ts +12 -0
  378. package/lib/typescript/commonjs/src/web/Icon.d.ts.map +1 -0
  379. package/lib/typescript/commonjs/src/web/NativeTheme.d.ts +10 -0
  380. package/lib/typescript/commonjs/src/web/NativeTheme.d.ts.map +1 -0
  381. package/lib/typescript/commonjs/src/web/NavigationRail.d.ts +8 -0
  382. package/lib/typescript/commonjs/src/web/NavigationRail.d.ts.map +1 -0
  383. package/lib/typescript/commonjs/src/web/PlatformComponentsProvider.d.ts +10 -0
  384. package/lib/typescript/commonjs/src/web/PlatformComponentsProvider.d.ts.map +1 -0
  385. package/lib/typescript/commonjs/src/web/SegmentedControl.d.ts +8 -0
  386. package/lib/typescript/commonjs/src/web/SegmentedControl.d.ts.map +1 -0
  387. package/lib/typescript/commonjs/src/web/SelectionMenu.d.ts +10 -0
  388. package/lib/typescript/commonjs/src/web/SelectionMenu.d.ts.map +1 -0
  389. package/lib/typescript/commonjs/src/web/SplitButton.d.ts +9 -0
  390. package/lib/typescript/commonjs/src/web/SplitButton.d.ts.map +1 -0
  391. package/lib/typescript/commonjs/src/web/TabBar.d.ts +9 -0
  392. package/lib/typescript/commonjs/src/web/TabBar.d.ts.map +1 -0
  393. package/lib/typescript/commonjs/src/web/TextField.d.ts +9 -0
  394. package/lib/typescript/commonjs/src/web/TextField.d.ts.map +1 -0
  395. package/lib/typescript/commonjs/src/web/components.d.ts +17 -0
  396. package/lib/typescript/commonjs/src/web/components.d.ts.map +1 -0
  397. package/lib/typescript/commonjs/src/web/containers.d.ts +24 -0
  398. package/lib/typescript/commonjs/src/web/containers.d.ts.map +1 -0
  399. package/lib/typescript/commonjs/src/web/dateInput.d.ts +20 -0
  400. package/lib/typescript/commonjs/src/web/dateInput.d.ts.map +1 -0
  401. package/lib/typescript/commonjs/src/web/shared.d.ts +43 -0
  402. package/lib/typescript/commonjs/src/web/shared.d.ts.map +1 -0
  403. package/lib/typescript/commonjs/src/webComponents.d.ts +43 -0
  404. package/lib/typescript/commonjs/src/webComponents.d.ts.map +1 -0
  405. package/lib/typescript/module/docs/sidebars.d.ts.map +1 -1
  406. package/lib/typescript/module/src/Button.d.ts +111 -3
  407. package/lib/typescript/module/src/Button.d.ts.map +1 -1
  408. package/lib/typescript/module/src/ButtonGroup.d.ts +21 -6
  409. package/lib/typescript/module/src/ButtonGroup.d.ts.map +1 -1
  410. package/lib/typescript/module/src/ButtonGroupNativeComponent.d.ts +64 -3
  411. package/lib/typescript/module/src/ButtonGroupNativeComponent.d.ts.map +1 -1
  412. package/lib/typescript/module/src/ButtonNativeComponent.d.ts +94 -3
  413. package/lib/typescript/module/src/ButtonNativeComponent.d.ts.map +1 -1
  414. package/lib/typescript/module/src/ContextMenu.d.ts +20 -33
  415. package/lib/typescript/module/src/ContextMenu.d.ts.map +1 -1
  416. package/lib/typescript/module/src/ContextMenuNativeComponent.d.ts +45 -47
  417. package/lib/typescript/module/src/ContextMenuNativeComponent.d.ts.map +1 -1
  418. package/lib/typescript/module/src/DatePicker.d.ts +74 -1
  419. package/lib/typescript/module/src/DatePicker.d.ts.map +1 -1
  420. package/lib/typescript/module/src/DatePickerNativeComponent.d.ts +16 -1
  421. package/lib/typescript/module/src/DatePickerNativeComponent.d.ts.map +1 -1
  422. package/lib/typescript/module/src/FloatingActionButton.d.ts +67 -0
  423. package/lib/typescript/module/src/FloatingActionButton.d.ts.map +1 -0
  424. package/lib/typescript/module/src/FloatingActionButtonNativeComponent.d.ts +43 -0
  425. package/lib/typescript/module/src/FloatingActionButtonNativeComponent.d.ts.map +1 -0
  426. package/lib/typescript/module/src/FloatingToolbar.d.ts +30 -0
  427. package/lib/typescript/module/src/FloatingToolbar.d.ts.map +1 -1
  428. package/lib/typescript/module/src/FloatingToolbarNativeComponent.d.ts +12 -0
  429. package/lib/typescript/module/src/FloatingToolbarNativeComponent.d.ts.map +1 -1
  430. package/lib/typescript/module/src/LiquidGlass.d.ts +17 -0
  431. package/lib/typescript/module/src/LiquidGlass.d.ts.map +1 -1
  432. package/lib/typescript/module/src/LiquidGlassContainer.d.ts +27 -0
  433. package/lib/typescript/module/src/LiquidGlassContainer.d.ts.map +1 -0
  434. package/lib/typescript/module/src/LiquidGlassContainerNativeComponent.d.ts +13 -0
  435. package/lib/typescript/module/src/LiquidGlassContainerNativeComponent.d.ts.map +1 -0
  436. package/lib/typescript/module/src/LiquidGlassNativeComponent.d.ts +7 -0
  437. package/lib/typescript/module/src/LiquidGlassNativeComponent.d.ts.map +1 -1
  438. package/lib/typescript/module/src/NavigationRail.d.ts +86 -0
  439. package/lib/typescript/module/src/NavigationRail.d.ts.map +1 -0
  440. package/lib/typescript/module/src/NavigationRailNativeComponent.d.ts +81 -0
  441. package/lib/typescript/module/src/NavigationRailNativeComponent.d.ts.map +1 -0
  442. package/lib/typescript/module/src/PlatformComponentsProvider.d.ts +6 -0
  443. package/lib/typescript/module/src/PlatformComponentsProvider.d.ts.map +1 -0
  444. package/lib/typescript/module/src/SegmentedControl.d.ts +18 -1
  445. package/lib/typescript/module/src/SegmentedControl.d.ts.map +1 -1
  446. package/lib/typescript/module/src/SegmentedControlNativeComponent.d.ts +9 -0
  447. package/lib/typescript/module/src/SegmentedControlNativeComponent.d.ts.map +1 -1
  448. package/lib/typescript/module/src/SelectionMenu.d.ts +44 -2
  449. package/lib/typescript/module/src/SelectionMenu.d.ts.map +1 -1
  450. package/lib/typescript/module/src/SelectionMenuNativeComponent.d.ts +20 -4
  451. package/lib/typescript/module/src/SelectionMenuNativeComponent.d.ts.map +1 -1
  452. package/lib/typescript/module/src/SplitButton.d.ts +83 -0
  453. package/lib/typescript/module/src/SplitButton.d.ts.map +1 -0
  454. package/lib/typescript/module/src/TabBar.d.ts +144 -0
  455. package/lib/typescript/module/src/TabBar.d.ts.map +1 -0
  456. package/lib/typescript/module/src/TabBarAccessoryNativeComponent.d.ts +13 -0
  457. package/lib/typescript/module/src/TabBarAccessoryNativeComponent.d.ts.map +1 -0
  458. package/lib/typescript/module/src/TabBarNativeComponent.d.ts +110 -0
  459. package/lib/typescript/module/src/TabBarNativeComponent.d.ts.map +1 -0
  460. package/lib/typescript/module/src/TextField.d.ts +166 -8
  461. package/lib/typescript/module/src/TextField.d.ts.map +1 -1
  462. package/lib/typescript/module/src/TextFieldNativeComponent.d.ts +81 -3
  463. package/lib/typescript/module/src/TextFieldNativeComponent.d.ts.map +1 -1
  464. package/lib/typescript/module/src/focusRegistry.d.ts.map +1 -1
  465. package/lib/typescript/module/src/haptics.d.ts +20 -0
  466. package/lib/typescript/module/src/haptics.d.ts.map +1 -0
  467. package/lib/typescript/module/src/icons.d.ts +7 -1
  468. package/lib/typescript/module/src/icons.d.ts.map +1 -1
  469. package/lib/typescript/module/src/index.d.ts +7 -0
  470. package/lib/typescript/module/src/index.d.ts.map +1 -1
  471. package/lib/typescript/module/src/index.web.d.ts +25 -254
  472. package/lib/typescript/module/src/index.web.d.ts.map +1 -1
  473. package/lib/typescript/module/src/jest.d.ts +139 -0
  474. package/lib/typescript/module/src/jest.d.ts.map +1 -0
  475. package/lib/typescript/module/src/menuItems.d.ts +100 -0
  476. package/lib/typescript/module/src/menuItems.d.ts.map +1 -0
  477. package/lib/typescript/module/src/selectionIdentifiers.d.ts +3 -0
  478. package/lib/typescript/module/src/selectionIdentifiers.d.ts.map +1 -0
  479. package/lib/typescript/module/src/submitBehavior.d.ts +9 -0
  480. package/lib/typescript/module/src/submitBehavior.d.ts.map +1 -0
  481. package/lib/typescript/module/src/tabItems.d.ts +55 -0
  482. package/lib/typescript/module/src/tabItems.d.ts.map +1 -0
  483. package/lib/typescript/module/src/web/Button.d.ts +9 -0
  484. package/lib/typescript/module/src/web/Button.d.ts.map +1 -0
  485. package/lib/typescript/module/src/web/ButtonGroup.d.ts +11 -0
  486. package/lib/typescript/module/src/web/ButtonGroup.d.ts.map +1 -0
  487. package/lib/typescript/module/src/web/DatePicker.d.ts +14 -0
  488. package/lib/typescript/module/src/web/DatePicker.d.ts.map +1 -0
  489. package/lib/typescript/module/src/web/DateRangePicker.d.ts +11 -0
  490. package/lib/typescript/module/src/web/DateRangePicker.d.ts.map +1 -0
  491. package/lib/typescript/module/src/web/Dialog.d.ts +12 -0
  492. package/lib/typescript/module/src/web/Dialog.d.ts.map +1 -0
  493. package/lib/typescript/module/src/web/FloatingActionButton.d.ts +8 -0
  494. package/lib/typescript/module/src/web/FloatingActionButton.d.ts.map +1 -0
  495. package/lib/typescript/module/src/web/Icon.d.ts +12 -0
  496. package/lib/typescript/module/src/web/Icon.d.ts.map +1 -0
  497. package/lib/typescript/module/src/web/NativeTheme.d.ts +10 -0
  498. package/lib/typescript/module/src/web/NativeTheme.d.ts.map +1 -0
  499. package/lib/typescript/module/src/web/NavigationRail.d.ts +8 -0
  500. package/lib/typescript/module/src/web/NavigationRail.d.ts.map +1 -0
  501. package/lib/typescript/module/src/web/PlatformComponentsProvider.d.ts +10 -0
  502. package/lib/typescript/module/src/web/PlatformComponentsProvider.d.ts.map +1 -0
  503. package/lib/typescript/module/src/web/SegmentedControl.d.ts +8 -0
  504. package/lib/typescript/module/src/web/SegmentedControl.d.ts.map +1 -0
  505. package/lib/typescript/module/src/web/SelectionMenu.d.ts +10 -0
  506. package/lib/typescript/module/src/web/SelectionMenu.d.ts.map +1 -0
  507. package/lib/typescript/module/src/web/SplitButton.d.ts +9 -0
  508. package/lib/typescript/module/src/web/SplitButton.d.ts.map +1 -0
  509. package/lib/typescript/module/src/web/TabBar.d.ts +9 -0
  510. package/lib/typescript/module/src/web/TabBar.d.ts.map +1 -0
  511. package/lib/typescript/module/src/web/TextField.d.ts +9 -0
  512. package/lib/typescript/module/src/web/TextField.d.ts.map +1 -0
  513. package/lib/typescript/module/src/web/components.d.ts +17 -0
  514. package/lib/typescript/module/src/web/components.d.ts.map +1 -0
  515. package/lib/typescript/module/src/web/containers.d.ts +24 -0
  516. package/lib/typescript/module/src/web/containers.d.ts.map +1 -0
  517. package/lib/typescript/module/src/web/dateInput.d.ts +20 -0
  518. package/lib/typescript/module/src/web/dateInput.d.ts.map +1 -0
  519. package/lib/typescript/module/src/web/shared.d.ts +43 -0
  520. package/lib/typescript/module/src/web/shared.d.ts.map +1 -0
  521. package/lib/typescript/module/src/webComponents.d.ts +43 -0
  522. package/lib/typescript/module/src/webComponents.d.ts.map +1 -0
  523. package/package.json +32 -2
  524. package/plugin/build/index.d.ts +3 -2
  525. package/plugin/build/index.d.ts.map +1 -1
  526. package/react-native.config.js +3 -0
  527. package/shared/PCFloatingActionButtonComponentDescriptors-custom.h +22 -0
  528. package/shared/PCFloatingActionButtonShadowNode-custom.cpp +48 -0
  529. package/shared/PCFloatingActionButtonShadowNode-custom.h +54 -0
  530. package/shared/PCFloatingActionButtonState-custom.h +61 -0
  531. package/shared/PCNavigationRailComponentDescriptors-custom.h +47 -0
  532. package/shared/PCNavigationRailShadowNode-custom.h +42 -0
  533. package/shared/PCNavigationRailState-custom.h +62 -0
  534. package/shared/PCTabBarComponentDescriptors-custom.h +22 -0
  535. package/shared/PCTabBarShadowNode-custom.cpp +54 -0
  536. package/shared/PCTabBarShadowNode-custom.h +54 -0
  537. package/shared/PCTabBarState-custom.h +62 -0
  538. package/shared/react/renderer/components/PlatformComponentsViewSpec/ComponentDescriptors.h +3 -0
  539. package/src/Button.tsx +222 -7
  540. package/src/ButtonGroup.tsx +29 -11
  541. package/src/ButtonGroupNativeComponent.ts +74 -4
  542. package/src/ButtonNativeComponent.ts +119 -3
  543. package/src/ContextMenu.tsx +34 -81
  544. package/src/ContextMenuNativeComponent.ts +47 -49
  545. package/src/DatePicker.tsx +182 -4
  546. package/src/DatePickerNativeComponent.ts +19 -2
  547. package/src/FloatingActionButton.tsx +121 -0
  548. package/src/FloatingActionButtonNativeComponent.ts +58 -0
  549. package/src/FloatingToolbar.tsx +54 -3
  550. package/src/FloatingToolbarNativeComponent.ts +16 -0
  551. package/src/LiquidGlass.tsx +24 -1
  552. package/src/LiquidGlassContainer.tsx +42 -0
  553. package/src/LiquidGlassContainerNativeComponent.ts +17 -0
  554. package/src/LiquidGlassNativeComponent.ts +8 -0
  555. package/src/NavigationRail.tsx +203 -0
  556. package/src/NavigationRailNativeComponent.ts +107 -0
  557. package/src/PlatformComponentsProvider.tsx +15 -0
  558. package/src/SegmentedControl.tsx +31 -1
  559. package/src/SegmentedControlNativeComponent.ts +11 -0
  560. package/src/SelectionMenu.tsx +81 -6
  561. package/src/SelectionMenuNativeComponent.ts +26 -4
  562. package/src/SplitButton.tsx +198 -0
  563. package/src/TabBar.tsx +373 -0
  564. package/src/TabBarAccessoryNativeComponent.ts +19 -0
  565. package/src/TabBarNativeComponent.ts +145 -0
  566. package/src/TextField.tsx +428 -14
  567. package/src/TextFieldNativeComponent.ts +110 -4
  568. package/src/focusRegistry.ts +1 -0
  569. package/src/haptics.ts +31 -0
  570. package/src/icons.ts +28 -1
  571. package/src/index.tsx +7 -0
  572. package/src/index.web.tsx +51 -391
  573. package/src/jest.tsx +958 -0
  574. package/src/menuItems.ts +152 -0
  575. package/src/selectionIdentifiers.ts +17 -0
  576. package/src/submitBehavior.ts +16 -0
  577. package/src/tabItems.ts +113 -0
  578. package/src/web/Button.tsx +201 -0
  579. package/src/web/ButtonGroup.tsx +154 -0
  580. package/src/web/DatePicker.tsx +204 -0
  581. package/src/web/DateRangePicker.tsx +135 -0
  582. package/src/web/Dialog.tsx +54 -0
  583. package/src/web/FloatingActionButton.tsx +76 -0
  584. package/src/web/Icon.tsx +33 -0
  585. package/src/web/NativeTheme.ts +23 -0
  586. package/src/web/NavigationRail.tsx +148 -0
  587. package/src/web/PlatformComponentsProvider.tsx +48 -0
  588. package/src/web/SegmentedControl.tsx +127 -0
  589. package/src/web/SelectionMenu.tsx +149 -0
  590. package/src/web/SplitButton.tsx +32 -0
  591. package/src/web/TabBar.tsx +150 -0
  592. package/src/web/TextField.tsx +361 -0
  593. package/src/web/components.tsx +71 -0
  594. package/src/web/containers.tsx +130 -0
  595. package/src/web/dateInput.ts +137 -0
  596. package/src/web/shared.ts +176 -0
  597. package/src/webComponents.ts +45 -0
  598. package/android/src/main/java/com/platformcomponents/DateConstraints.kt +0 -83
@@ -9,7 +9,7 @@ import UIKit
9
9
  /// saw, and a push older than the latest edit is dropped so a slow JS
10
10
  /// round-trip can't erase what was typed meanwhile.
11
11
  @objcMembers
12
- public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDelegate {
12
+ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDelegate, UIGestureRecognizerDelegate {
13
13
  // MARK: - Props (set from ObjC++)
14
14
 
15
15
  public var label: String = "" {
@@ -83,6 +83,21 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
83
83
  didSet { if oldValue != multiline { rebuildInput() } }
84
84
  }
85
85
 
86
+ /// What the return key does: "submit" | "blurAndSubmit" | "newline";
87
+ /// "" keeps the defaults (a newline in multi-line fields, else blur and submit)
88
+ public var submitBehavior: String = ""
89
+
90
+ /// UITextInputPasswordRules descriptor; "" = none
91
+ public var passwordRules: String = "" {
92
+ didSet { if oldValue != passwordRules { applyTraits() } }
93
+ }
94
+
95
+ /// The keyboard toolbar's items (kind, itemId, title, systemItem, icon
96
+ /// fields, prominent, accessibilityLabel, testID); empty = no toolbar
97
+ public var keyboardToolbarItems: [NSDictionary] = [] {
98
+ didSet { if oldValue != keyboardToolbarItems { applyKeyboardToolbar() } }
99
+ }
100
+
86
101
  /// "enabled" | "disabled"
87
102
  public var interactivity: String = "enabled" {
88
103
  didSet { if oldValue != interactivity { applyEnabled() } }
@@ -155,7 +170,86 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
155
170
  didSet { if oldValue != labelWidth { setNeedsLayout(); onNeedsRemeasure?() } }
156
171
  }
157
172
 
173
+ /// Test id of the inner input, where E2E drivers type (see PCTextField.mm)
174
+ public var inputTestID: String = "" {
175
+ didSet { if oldValue != inputTestID { applyAccessibility() } }
176
+ }
177
+
178
+ public var leadingIconTestID: String = "" {
179
+ didSet { if oldValue != leadingIconTestID { applyAccessories() } }
180
+ }
181
+
182
+ public var leadingIconSpokenLabel: String = "" {
183
+ didSet { if oldValue != leadingIconSpokenLabel { applyAccessories() } }
184
+ }
185
+
186
+ public var trailingIconTestID: String = "" {
187
+ didSet { if oldValue != trailingIconTestID { applyAccessories() } }
188
+ }
189
+
190
+ public var trailingIconSpokenLabel: String = "" {
191
+ didSet { if oldValue != trailingIconSpokenLabel { applyAccessories() } }
192
+ }
193
+
194
+ // Colors; nil keeps the system color
195
+ public var activeColor: UIColor? {
196
+ didSet { if oldValue != activeColor { applyBorderStyle(); applyColors() } }
197
+ }
198
+
199
+ public var outlineColor: UIColor? {
200
+ didSet { if oldValue != outlineColor { applyBorderStyle(); applyColors() } }
201
+ }
202
+
203
+ public var errorColor: UIColor? {
204
+ didSet { if oldValue != errorColor { applyColors() } }
205
+ }
206
+
207
+ public var containerColor: UIColor? {
208
+ didSet { if oldValue != containerColor { applyBorderStyle(); applyColors() } }
209
+ }
210
+
211
+ public var textColor: UIColor? {
212
+ didSet { if oldValue != textColor { applyColors() } }
213
+ }
214
+
215
+ public var placeholderTextColor: UIColor? {
216
+ didSet { if oldValue != placeholderTextColor { applyPlaceholder() } }
217
+ }
218
+
219
+ /// Cap on the Dynamic Type scale of every text; values below 1 mean no cap
220
+ public var maxFontSizeMultiplier: CGFloat = 0 {
221
+ didSet { if oldValue != maxFontSizeMultiplier { applyFonts(); applyAccessories() } }
222
+ }
223
+
224
+ /// "" | "left" | "center" | "right"
225
+ public var textAlign: String = "" {
226
+ didSet { if oldValue != textAlign { applyAlignment() } }
227
+ }
228
+
229
+ /// Multi-line height bounds in lines; 0 = unset
230
+ public var minLines: Int = 0 {
231
+ didSet { if oldValue != minLines { setNeedsLayout(); onNeedsRemeasure?() } }
232
+ }
233
+
234
+ public var maxLines: Int = 0 {
235
+ didSet { if oldValue != maxLines { setNeedsLayout(); onNeedsRemeasure?() } }
236
+ }
237
+
238
+ /// A non-editable field that reports presses, as a picker's anchor
239
+ public var pressable: Bool = false {
240
+ didSet { if oldValue != pressable { applyEnabled() } }
241
+ }
242
+
158
243
  private var leadingLabel: Bool { labelPlacement == "leading" && !label.isEmpty }
244
+
245
+ /// Whether the field acts as a button: pressable and not editable
246
+ private var actsAsButton: Bool { pressable && interactivity == "disabled" }
247
+
248
+ /// The rounded rect drawn here instead of by UIKit, whose border can't be colored
249
+ private var drawsBox: Bool {
250
+ (borderStyle.isEmpty || borderStyle == "roundedRect")
251
+ && (activeColor != nil || outlineColor != nil || containerColor != nil)
252
+ }
159
253
  private var leadingColumnWidth: CGFloat { labelWidth > 0 ? labelWidth : 100 }
160
254
  private let leadingSpacing: CGFloat = 8
161
255
 
@@ -164,7 +258,10 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
164
258
  public var onChange: ((String, Int) -> Void)?
165
259
  public var onFocusChange: ((Bool, String) -> Void)?
166
260
  public var onSubmit: ((String) -> Void)?
261
+ public var onSelectionChange: ((Int, Int) -> Void)?
262
+ public var onKeyboardToolbarPress: ((String) -> Void)?
167
263
  public var onTrailingIconPress: (() -> Void)?
264
+ public var onPress: (() -> Void)?
168
265
 
169
266
  /// Called when content changed in a way that affects the height (a
170
267
  /// multi-line edit, an error message appearing), so the Fabric
@@ -181,10 +278,19 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
181
278
  /// Whether a secure field is currently showing its text (password toggle)
182
279
  private var revealed = false
183
280
 
281
+ /// The selection JS last heard of, so a change is reported once. Changes
282
+ /// made from JS (text or selection) update it without an event.
283
+ private var lastReportedSelection = NSRange(location: NSNotFound, length: 0)
284
+ private var applyingFromJS = false
285
+
286
+ /// The toolbar above the keyboard, while there are toolbar items
287
+ private var keyboardToolbar: UIToolbar?
288
+ private var toolbarGeneration = 0
289
+
184
290
  // MARK: - Subviews
185
291
 
186
292
  private let captionLabel = UILabel()
187
- private let textField = UITextField()
293
+ private let textField = PCInputTextField()
188
294
  private let textView = PCPlaceholderTextView()
189
295
  private let footerLabel = UILabel()
190
296
  private let counterLabel = UILabel()
@@ -193,7 +299,8 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
193
299
  private var trailingIcon = PCButtonSupport.Icon.none
194
300
  private var leadingImage: UIImage?
195
301
  private var trailingImage: UIImage?
196
- private var iconGeneration = 0
302
+ private var leadingIconGeneration = 0
303
+ private var trailingIconGeneration = 0
197
304
 
198
305
  // MARK: - Layout metrics
199
306
 
@@ -243,6 +350,14 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
243
350
  addSubview(footerLabel)
244
351
  addSubview(counterLabel)
245
352
 
353
+ // Presses of a field that acts as a button; accessory buttons keep their own
354
+ pressRecognizer.addTarget(self, action: #selector(handlePress))
355
+ pressRecognizer.delegate = self
356
+ pressRecognizer.isEnabled = false
357
+ addGestureRecognizer(pressRecognizer)
358
+ textField.onActivate = { [weak self] in self?.activateAsButton() ?? false }
359
+ textView.onActivate = { [weak self] in self?.activateAsButton() ?? false }
360
+
246
361
  applyFonts()
247
362
  applyColors()
248
363
  applyBorderStyle()
@@ -252,6 +367,7 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
252
367
  applyFooter()
253
368
  applyAccessories()
254
369
  applyAccessibility()
370
+ applyAlignment()
255
371
  }
256
372
 
257
373
  // MARK: - Text
@@ -279,6 +395,28 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
279
395
  guard !text.isEmpty else { return }
280
396
  setTextInternal("", moveCursorToEnd: true)
281
397
  reportEdit()
398
+ lastReportedSelection = NSRange(location: NSNotFound, length: 0)
399
+ reportSelection()
400
+ }
401
+
402
+ /// A selection from JS, in UTF-16 offsets clamped to the text. Dropped
403
+ /// like a stale `setText` when the user has edited since `eventCount`.
404
+ public func setSelection(start: Int, end: Int, eventCount: Int) {
405
+ guard eventCount >= nativeEventCount else { return }
406
+ guard activeInput.markedTextRange == nil else { return }
407
+ let length = (text as NSString).length
408
+ let from = min(max(0, start), length)
409
+ let to = min(max(from, end), length)
410
+ let range = NSRange(location: from, length: to - from)
411
+ applyingFromJS = true
412
+ if multiline {
413
+ textView.selectedRange = range
414
+ } else if let startPosition = textField.position(from: textField.beginningOfDocument, offset: from),
415
+ let endPosition = textField.position(from: startPosition, offset: to - from) {
416
+ textField.selectedTextRange = textField.textRange(from: startPosition, to: endPosition)
417
+ }
418
+ applyingFromJS = false
419
+ lastReportedSelection = currentSelection() ?? range
282
420
  }
283
421
 
284
422
  public func focus() {
@@ -289,28 +427,28 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
289
427
  activeInput.resignFirstResponder()
290
428
  }
291
429
 
292
- public func setLeadingIcon(type: String, name: String, uri: String, scale: CGFloat, tinted: Bool) {
293
- let next = PCButtonSupport.Icon(type: type, name: name, uri: uri, scale: scale, tinted: tinted)
430
+ public func setLeadingIcon(type: String, name: String, request: String, uri: String, scale: CGFloat, tinted: Bool) {
431
+ let next = PCButtonSupport.Icon(type: type, name: name, uri: uri, scale: scale, tinted: tinted, request: request)
294
432
  guard next != leadingIcon else { return }
295
433
  leadingIcon = next
296
- iconGeneration += 1
297
- let generation = iconGeneration
434
+ leadingIconGeneration += 1
435
+ let generation = leadingIconGeneration
298
436
  leadingImage = PCButtonSupport.image(for: next) { [weak self] image in
299
- guard let self, self.iconGeneration == generation else { return }
437
+ guard let self, self.leadingIconGeneration == generation else { return }
300
438
  self.leadingImage = image
301
439
  self.applyAccessories()
302
440
  }
303
441
  applyAccessories()
304
442
  }
305
443
 
306
- public func setTrailingIcon(type: String, name: String, uri: String, scale: CGFloat, tinted: Bool) {
307
- let next = PCButtonSupport.Icon(type: type, name: name, uri: uri, scale: scale, tinted: tinted)
444
+ public func setTrailingIcon(type: String, name: String, request: String, uri: String, scale: CGFloat, tinted: Bool) {
445
+ let next = PCButtonSupport.Icon(type: type, name: name, uri: uri, scale: scale, tinted: tinted, request: request)
308
446
  guard next != trailingIcon else { return }
309
447
  trailingIcon = next
310
- iconGeneration += 1
311
- let generation = iconGeneration
448
+ trailingIconGeneration += 1
449
+ let generation = trailingIconGeneration
312
450
  trailingImage = PCButtonSupport.image(for: next) { [weak self] image in
313
- guard let self, self.iconGeneration == generation else { return }
451
+ guard let self, self.trailingIconGeneration == generation else { return }
314
452
  self.trailingImage = image
315
453
  self.applyAccessories()
316
454
  }
@@ -320,11 +458,14 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
320
458
  /// Resets the text state for a recycled view.
321
459
  public func resetForRecycle() {
322
460
  blur()
461
+ leadingIconGeneration += 1
462
+ trailingIconGeneration += 1
323
463
  initialTextApplied = false
324
464
  autoFocusDone = false
325
465
  nativeEventCount = 0
326
466
  revealed = false
327
467
  setTextInternal("", moveCursorToEnd: true)
468
+ lastReportedSelection = NSRange(location: NSNotFound, length: 0)
328
469
  }
329
470
 
330
471
  private var activeInput: UIView & UITextInput {
@@ -332,6 +473,12 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
332
473
  }
333
474
 
334
475
  private func setTextInternal(_ value: String, moveCursorToEnd: Bool) {
476
+ // The cursor moves with the text; JS knows, so there is no event
477
+ applyingFromJS = true
478
+ defer {
479
+ applyingFromJS = false
480
+ lastReportedSelection = currentSelection() ?? lastReportedSelection
481
+ }
335
482
  if multiline {
336
483
  let wasAtEnd = textView.selectedRange.location == (textView.text as NSString).length
337
484
  let cursor = textView.selectedRange.location
@@ -366,6 +513,31 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
366
513
  onChange?(text, nativeEventCount)
367
514
  }
368
515
 
516
+ /// The selection in UTF-16 offsets, as JS strings count them.
517
+ private func currentSelection() -> NSRange? {
518
+ if multiline { return textView.selectedRange }
519
+ guard let range = textField.selectedTextRange else { return nil }
520
+ let start = textField.offset(from: textField.beginningOfDocument, to: range.start)
521
+ let end = textField.offset(from: textField.beginningOfDocument, to: range.end)
522
+ return NSRange(location: start, length: max(0, end - start))
523
+ }
524
+
525
+ private func reportSelection() {
526
+ guard !applyingFromJS, let selection = currentSelection(), selection != lastReportedSelection else { return }
527
+ lastReportedSelection = selection
528
+ onSelectionChange?(selection.location, selection.location + selection.length)
529
+ }
530
+
531
+ /// The return key: submits unless it inserts a newline, and blurs
532
+ /// unless submitBehavior is "submit". Returns whether it submitted.
533
+ private func handleReturn() -> Bool {
534
+ let behavior = submitBehavior.isEmpty ? (multiline ? "newline" : "blurAndSubmit") : submitBehavior
535
+ if multiline && behavior == "newline" { return false }
536
+ onSubmit?(text)
537
+ if behavior != "submit" { activeInput.resignFirstResponder() }
538
+ return true
539
+ }
540
+
369
541
  // MARK: - UITextField
370
542
 
371
543
  @objc private func fieldEditingChanged() {
@@ -387,17 +559,69 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
387
559
  onFocusChange?(false, text)
388
560
  }
389
561
 
562
+ public func textFieldShouldBeginEditing(_ field: UITextField) -> Bool {
563
+ !actsAsButton
564
+ }
565
+
566
+ /// The clear button doesn't send .editingChanged on every iOS version;
567
+ /// report the cleared text once UIKit has applied it, unless it did.
568
+ public func textFieldShouldClear(_ field: UITextField) -> Bool {
569
+ DispatchQueue.main.async { [weak self] in
570
+ guard let self, (self.textField.text ?? "") != self.text else { return }
571
+ self.fieldEditingChanged()
572
+ }
573
+ return true
574
+ }
575
+
390
576
  public func textFieldShouldReturn(_ field: UITextField) -> Bool {
391
- onSubmit?(text)
392
- // Single-line fields blur on submit, as the core TextInput does by default
393
- field.resignFirstResponder()
577
+ // Single-line fields blur on submit unless submitBehavior is
578
+ // "submit", as the core TextInput does
579
+ _ = handleReturn()
394
580
  return true
395
581
  }
396
582
 
583
+ public func textFieldDidChangeSelection(_ field: UITextField) {
584
+ reportSelection()
585
+ }
586
+
397
587
  public func textField(_ field: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
398
588
  allowsChange(current: field.text ?? "", range: range, replacement: string, input: field)
399
589
  }
400
590
 
591
+ // MARK: - Press (a field acting as a button)
592
+
593
+ private let pressRecognizer = UITapGestureRecognizer()
594
+
595
+ @objc private func handlePress() {
596
+ guard actsAsButton else { return }
597
+ // Brief highlight, as a plain UIButton gives
598
+ activeInput.alpha = 0.5
599
+ UIView.animate(withDuration: 0.25, delay: 0.05, options: [.allowUserInteraction]) {
600
+ self.activeInput.alpha = 1
601
+ }
602
+ onPress?()
603
+ }
604
+
605
+ private func activateAsButton() -> Bool {
606
+ guard actsAsButton else { return false }
607
+ onPress?()
608
+ return true
609
+ }
610
+
611
+ public func gestureRecognizer(_ recognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
612
+ // The trailing icon and the password toggle handle their own taps
613
+ !(touch.view is PCAccessoryButton)
614
+ }
615
+
616
+ /// UITextField's own tap recognizers would otherwise take the tap (and
617
+ /// then find editing refused), leaving the press unreported
618
+ public func gestureRecognizer(
619
+ _ recognizer: UIGestureRecognizer,
620
+ shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer
621
+ ) -> Bool {
622
+ recognizer === pressRecognizer
623
+ }
624
+
401
625
  // MARK: - UITextView
402
626
 
403
627
  public func textViewDidChange(_ view: UITextView) {
@@ -422,7 +646,15 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
422
646
  }
423
647
 
424
648
  public func textView(_ view: UITextView, shouldChangeTextIn range: NSRange, replacementText string: String) -> Bool {
425
- allowsChange(current: view.text ?? "", range: range, replacement: string, input: view)
649
+ // Return submits instead of inserting a newline, unless submitBehavior is "newline"
650
+ if string == "\n", view.markedTextRange == nil, handleReturn() {
651
+ return false
652
+ }
653
+ return allowsChange(current: view.text ?? "", range: range, replacement: string, input: view)
654
+ }
655
+
656
+ public func textViewDidChangeSelection(_ view: UITextView) {
657
+ reportSelection()
426
658
  }
427
659
 
428
660
  /// Enforces `maxLength` in UTF-16 units, like the core TextInput and the
@@ -437,7 +669,15 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
437
669
  // MARK: - Props handling
438
670
 
439
671
  private func rebuildInput() {
440
- let focused = activeInput.isFirstResponder
672
+ // `multiline` already has its new value in didSet, so activeInput
673
+ // points to the replacement. Read focus and selection from the old input.
674
+ let previousInput: UIView & UITextInput = multiline ? textField : textView
675
+ let focused = previousInput.isFirstResponder
676
+ let selection = previousInput.selectedTextRange.map { range in
677
+ let start = previousInput.offset(from: previousInput.beginningOfDocument, to: range.start)
678
+ let end = previousInput.offset(from: previousInput.beginningOfDocument, to: range.end)
679
+ return NSRange(location: start, length: max(0, end - start))
680
+ }
441
681
  textField.removeFromSuperview()
442
682
  textView.removeFromSuperview()
443
683
  if multiline {
@@ -456,7 +696,11 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
456
696
  applyAccessories()
457
697
  applyEnabled()
458
698
  applyAccessibility()
699
+ applyAlignment()
459
700
  if focused { activeInput.becomeFirstResponder() }
701
+ if let selection {
702
+ setSelection(start: selection.location, end: NSMaxRange(selection), eventCount: nativeEventCount)
703
+ }
460
704
  setNeedsLayout()
461
705
  onNeedsRemeasure?()
462
706
  }
@@ -470,8 +714,18 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
470
714
  }
471
715
 
472
716
  private func applyPlaceholder() {
473
- textField.placeholder = placeholder.isEmpty ? nil : placeholder
717
+ if placeholder.isEmpty {
718
+ textField.placeholder = nil
719
+ } else if let placeholderTextColor {
720
+ textField.attributedPlaceholder = NSAttributedString(
721
+ string: placeholder,
722
+ attributes: [.foregroundColor: placeholderTextColor]
723
+ )
724
+ } else {
725
+ textField.placeholder = placeholder
726
+ }
474
727
  textView.placeholder = placeholder
728
+ textView.placeholderLabel.textColor = placeholderTextColor ?? .placeholderText
475
729
  }
476
730
 
477
731
  /// Supporting text, error message and counter below the field.
@@ -494,11 +748,33 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
494
748
  onNeedsRemeasure?()
495
749
  }
496
750
 
751
+ /// A text style's font at the current text size, capped at
752
+ /// `maxFontSizeMultiplier` times its size at the default text size.
753
+ /// `base` replaces the style's own font (the `textStyle` font, given at
754
+ /// its default-size point size), scaled along the style's curve.
755
+ private func scaledFont(_ style: UIFont.TextStyle, base: UIFont? = nil) -> UIFont {
756
+ let font = base ?? .preferredFont(
757
+ forTextStyle: style,
758
+ compatibleWith: UITraitCollection(preferredContentSizeCategory: .large)
759
+ )
760
+ let metrics = UIFontMetrics(forTextStyle: style)
761
+ if maxFontSizeMultiplier >= 1 {
762
+ return metrics.scaledFont(
763
+ for: font,
764
+ maximumPointSize: font.pointSize * maxFontSizeMultiplier,
765
+ compatibleWith: traitCollection
766
+ )
767
+ }
768
+ return metrics.scaledFont(for: font, compatibleWith: traitCollection)
769
+ }
770
+
771
+ private var inputFont: UIFont { scaledFont(.body, base: textFont) }
772
+
497
773
  private func applyFonts() {
498
- captionLabel.font = .preferredFont(forTextStyle: leadingLabel ? .body : .subheadline)
499
- footerLabel.font = .preferredFont(forTextStyle: .footnote)
500
- counterLabel.font = .preferredFont(forTextStyle: .footnote)
501
- let inputFont = textFont ?? .preferredFont(forTextStyle: .body)
774
+ captionLabel.font = scaledFont(leadingLabel ? .body : .subheadline)
775
+ footerLabel.font = scaledFont(.footnote)
776
+ counterLabel.font = scaledFont(.footnote)
777
+ let inputFont = self.inputFont
502
778
  textField.font = inputFont
503
779
  textView.font = inputFont
504
780
  textView.placeholderLabel.font = inputFont
@@ -509,14 +785,33 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
509
785
  private func applyColors() {
510
786
  let showsError = errorState == "error"
511
787
  let focused = activeInput.isFirstResponder
788
+ let error = errorColor ?? .systemRed
512
789
  captionLabel.textColor = showsError
513
- ? .systemRed
514
- : (leadingLabel ? .label : (focused ? tintColor : .secondaryLabel))
515
- footerLabel.textColor = showsError ? .systemRed : .secondaryLabel
790
+ ? error
791
+ : (leadingLabel ? .label : (focused ? (activeColor ?? tintColor) : .secondaryLabel))
792
+ footerLabel.textColor = showsError ? error : .secondaryLabel
516
793
  counterLabel.textColor = showsError && maxLength > 0 && (text as NSString).length > maxLength
517
- ? .systemRed
794
+ ? error
518
795
  : .secondaryLabel
519
- textView.layer.borderColor = (showsError ? UIColor.systemRed : UIColor.systemGray4).cgColor
796
+
797
+ // The outline of the text view and of a field whose box is drawn here
798
+ let outline: UIColor
799
+ if showsError {
800
+ outline = error
801
+ } else if focused {
802
+ outline = activeColor ?? outlineColor ?? .systemGray4
803
+ } else {
804
+ outline = outlineColor ?? .systemGray4
805
+ }
806
+ let resolved = outline.resolvedColor(with: traitCollection).cgColor
807
+ textView.layer.borderColor = resolved
808
+ textField.layer.borderColor = drawsBox ? resolved : nil
809
+
810
+ // Cursor and selection
811
+ textField.tintColor = activeColor
812
+ textView.tintColor = activeColor
813
+ textField.textColor = textColor ?? .label
814
+ textView.textColor = textColor ?? .label
520
815
  }
521
816
 
522
817
  private func applyBorderStyle() {
@@ -527,28 +822,69 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
527
822
  case "bezel": style = .bezel
528
823
  default: style = .roundedRect
529
824
  }
530
- textField.borderStyle = style
825
+ // UIKit's rounded rect can't take colors; draw its look instead
826
+ let drawsBox = self.drawsBox
827
+ textField.borderStyle = drawsBox ? .none : style
828
+ textField.layer.borderWidth = drawsBox ? 1 : 0
829
+ textField.layer.cornerRadius = drawsBox ? 5 : 0
830
+ textField.textInsets = drawsBox ? UIEdgeInsets(top: 0, left: 7, bottom: 0, right: 7) : .zero
831
+ if drawsBox {
832
+ textField.backgroundColor = containerColor ?? .systemBackground
833
+ } else {
834
+ textField.backgroundColor = style == .none ? containerColor : nil
835
+ }
531
836
  // The text view mirrors the rounded-rect field
532
837
  let bordered = style != .none
533
838
  textView.layer.borderWidth = bordered ? 1 : 0
534
839
  textView.layer.cornerRadius = style == .roundedRect ? 5 : 0
535
- textView.backgroundColor = bordered ? .systemBackground : .clear
840
+ textView.backgroundColor = containerColor ?? (bordered ? .systemBackground : .clear)
841
+ applyColors()
536
842
  setNeedsLayout()
537
843
  onNeedsRemeasure?()
538
844
  }
539
845
 
540
846
  private func applyEnabled() {
541
847
  let enabled = interactivity != "disabled"
542
- textField.isEnabled = enabled
848
+ let button = actsAsButton
849
+ // A field acting as a button keeps its enabled look; editing is
850
+ // refused in textFieldShouldBeginEditing and presses come from the
851
+ // gesture recognizer
852
+ textField.isEnabled = enabled || button
543
853
  textView.isEditable = enabled
544
854
  textView.isUserInteractionEnabled = enabled
545
- textView.alpha = enabled ? 1 : 0.5
855
+ textView.alpha = enabled || button ? 1 : 0.5
856
+ pressRecognizer.isEnabled = button
857
+ if button, activeInput.isFirstResponder { blur() }
858
+ applyAccessibility()
546
859
  }
547
860
 
548
861
  private func applyAccessibility() {
549
862
  let spoken = spokenLabel.isEmpty ? (label.isEmpty ? nil : label) : spokenLabel
550
863
  textField.accessibilityLabel = spoken
551
864
  textView.accessibilityLabel = spoken
865
+ let id = inputTestID.isEmpty ? nil : inputTestID
866
+ textField.accessibilityIdentifier = id
867
+ textView.accessibilityIdentifier = id
868
+ for input in [textField as UIView, textView] {
869
+ if actsAsButton {
870
+ input.accessibilityTraits.insert(.button)
871
+ } else {
872
+ input.accessibilityTraits.remove(.button)
873
+ }
874
+ }
875
+ }
876
+
877
+ private func applyAlignment() {
878
+ let alignment: NSTextAlignment
879
+ switch textAlign {
880
+ case "left": alignment = .left
881
+ case "center": alignment = .center
882
+ case "right": alignment = .right
883
+ default: alignment = .natural
884
+ }
885
+ textField.textAlignment = alignment
886
+ textView.textAlignment = alignment
887
+ textView.placeholderLabel.textAlignment = alignment
552
888
  }
553
889
 
554
890
  private func applyTraits() {
@@ -571,6 +907,7 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
571
907
  input.isSecureTextEntry = secureTextEntry && !revealed
572
908
  input.textContentType = PCTextFieldView.contentType(autoComplete)
573
909
  input.keyboardAppearance = PCTextFieldView.keyboardAppearance(keyboardAppearance)
910
+ input.passwordRules = passwordRules.isEmpty ? nil : UITextInputPasswordRules(descriptor: passwordRules)
574
911
  input.smartQuotesType = PCTextFieldView.smartQuotesType(smartQuotes)
575
912
  input.smartDashesType = PCTextFieldView.smartDashesType(smartDashes)
576
913
  input.smartInsertDeleteType = PCTextFieldView.smartInsertDeleteType(smartInsertDelete)
@@ -592,6 +929,7 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
592
929
  input.isSecureTextEntry = secureTextEntry && !revealed
593
930
  input.textContentType = PCTextFieldView.contentType(autoComplete)
594
931
  input.keyboardAppearance = PCTextFieldView.keyboardAppearance(keyboardAppearance)
932
+ input.passwordRules = passwordRules.isEmpty ? nil : UITextInputPasswordRules(descriptor: passwordRules)
595
933
  input.smartQuotesType = PCTextFieldView.smartQuotesType(smartQuotes)
596
934
  input.smartDashesType = PCTextFieldView.smartDashesType(smartDashes)
597
935
  input.smartInsertDeleteType = PCTextFieldView.smartInsertDeleteType(smartInsertDelete)
@@ -604,6 +942,102 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
604
942
  }
605
943
  }
606
944
 
945
+ /// The keyboard toolbar: a `UIToolbar` as the input's
946
+ /// `inputAccessoryView`, with the given buttons, flexible spaces and a
947
+ /// Done button that dismisses the keyboard.
948
+ private func applyKeyboardToolbar() {
949
+ toolbarGeneration += 1
950
+ if keyboardToolbarItems.isEmpty {
951
+ keyboardToolbar = nil
952
+ } else {
953
+ let toolbar = keyboardToolbar ?? UIToolbar(frame: CGRect(x: 0, y: 0, width: 320, height: 44))
954
+ toolbar.items = keyboardToolbarItems.map { toolbarItem(from: $0) }
955
+ toolbar.sizeToFit()
956
+ keyboardToolbar = toolbar
957
+ }
958
+ textField.inputAccessoryView = keyboardToolbar
959
+ textView.inputAccessoryView = keyboardToolbar
960
+ if activeInput.isFirstResponder {
961
+ activeInput.reloadInputViews()
962
+ }
963
+ }
964
+
965
+ private func toolbarItem(from spec: NSDictionary) -> UIBarButtonItem {
966
+ let kind = spec["kind"] as? String ?? "button"
967
+ if kind == "flexibleSpace" {
968
+ return .flexibleSpace()
969
+ }
970
+ let itemId = spec["itemId"] as? String ?? ""
971
+ let title = spec["title"] as? String ?? ""
972
+ let spokenLabel = spec["accessibilityLabel"] as? String ?? ""
973
+ let testID = spec["testID"] as? String ?? ""
974
+ let action: UIAction
975
+ if kind == "done" {
976
+ action = UIAction { [weak self] _ in self?.blur() }
977
+ } else {
978
+ action = UIAction { [weak self] _ in self?.onKeyboardToolbarPress?(itemId) }
979
+ }
980
+
981
+ let item: UIBarButtonItem
982
+ let systemItem = kind == "done" && title.isEmpty
983
+ ? UIBarButtonItem.SystemItem.done
984
+ : PCTextFieldView.toolbarSystemItem(spec["systemItem"] as? String ?? "")
985
+ let icon = PCButtonSupport.Icon(dictionary: spec as? [String: Any] ?? [:])
986
+ if let systemItem {
987
+ item = UIBarButtonItem(systemItem: systemItem, primaryAction: action)
988
+ } else if icon.isPresent {
989
+ item = UIBarButtonItem(title: nil, image: nil, primaryAction: action, menu: nil)
990
+ // The title is what VoiceOver reads for an icon button
991
+ item.accessibilityLabel = title.isEmpty ? nil : title
992
+ let generation = toolbarGeneration
993
+ item.image = PCButtonSupport.image(for: icon) { [weak self, weak item] image in
994
+ guard let self, self.toolbarGeneration == generation else { return }
995
+ item?.image = image
996
+ }
997
+ } else {
998
+ item = UIBarButtonItem(title: title, image: nil, primaryAction: action, menu: nil)
999
+ }
1000
+ if (spec["prominent"] as? String) == "true" {
1001
+ PCTextFieldView.makeProminent(item)
1002
+ }
1003
+ if !spokenLabel.isEmpty {
1004
+ item.accessibilityLabel = spokenLabel
1005
+ }
1006
+ item.accessibilityIdentifier = testID.isEmpty ? nil : testID
1007
+ return item
1008
+ }
1009
+
1010
+ /// The prominent style: tinted glass on iOS 26, bold before
1011
+ private static func makeProminent(_ item: UIBarButtonItem) {
1012
+ #if compiler(>=6.2)
1013
+ if #available(iOS 26.0, *) {
1014
+ item.style = .prominent
1015
+ return
1016
+ }
1017
+ #endif
1018
+ item.style = .done
1019
+ }
1020
+
1021
+ private static func toolbarSystemItem(_ value: String) -> UIBarButtonItem.SystemItem? {
1022
+ switch value {
1023
+ case "done": return .done
1024
+ case "cancel": return .cancel
1025
+ case "save": return .save
1026
+ case "add": return .add
1027
+ case "edit": return .edit
1028
+ case "close": return .close
1029
+ case "search": return .search
1030
+ case "compose": return .compose
1031
+ case "reply": return .reply
1032
+ case "action": return .action
1033
+ case "camera": return .camera
1034
+ case "trash": return .trash
1035
+ case "undo": return .undo
1036
+ case "redo": return .redo
1037
+ default: return nil
1038
+ }
1039
+ }
1040
+
607
1041
  /// Prefix and suffix labels, icons, the clear button and the password
608
1042
  /// toggle. Single-line only: a text view has no accessory views.
609
1043
  private func applyAccessories() {
@@ -612,7 +1046,10 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
612
1046
  textField.leftViewMode = .never
613
1047
  textField.rightViewMode = .never
614
1048
 
615
- if let leading = accessoryView(image: leadingImage, tinted: leadingIcon.tinted, text: prefix, action: nil) {
1049
+ if let leading = accessoryView(
1050
+ image: leadingImage, tinted: leadingIcon.tinted, text: prefix, action: nil,
1051
+ testID: leadingIconTestID, spokenLabel: leadingIconSpokenLabel
1052
+ ) {
616
1053
  textField.leftView = leading
617
1054
  textField.leftViewMode = .always
618
1055
  }
@@ -630,7 +1067,12 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
630
1067
  trailingAction = nil
631
1068
  }
632
1069
  let trailingTinted = trailingImage != nil ? trailingIcon.tinted : true
633
- if let trailing = accessoryView(image: trailingImageForSlot, tinted: trailingTinted, text: suffix, action: trailingAction) {
1070
+ let customTrailing = trailingImage != nil
1071
+ if let trailing = accessoryView(
1072
+ image: trailingImageForSlot, tinted: trailingTinted, text: suffix, action: trailingAction,
1073
+ testID: customTrailing ? trailingIconTestID : "",
1074
+ spokenLabel: customTrailing ? trailingIconSpokenLabel : ""
1075
+ ) {
634
1076
  textField.rightView = trailing
635
1077
  textField.rightViewMode = .always
636
1078
  }
@@ -646,7 +1088,10 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
646
1088
  }
647
1089
 
648
1090
  /// A label and/or an image button for the field's left or right slot.
649
- private func accessoryView(image: UIImage?, tinted: Bool, text: String, action: (() -> Void)?) -> UIView? {
1091
+ private func accessoryView(
1092
+ image: UIImage?, tinted: Bool, text: String, action: (() -> Void)?,
1093
+ testID: String, spokenLabel: String
1094
+ ) -> UIView? {
650
1095
  guard image != nil || !text.isEmpty else { return nil }
651
1096
  let stack = UIStackView()
652
1097
  stack.axis = .horizontal
@@ -656,7 +1101,7 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
656
1101
  stack.layoutMargins = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
657
1102
 
658
1103
  if let image {
659
- let config = UIImage.SymbolConfiguration(textStyle: .body)
1104
+ let config = UIImage.SymbolConfiguration(font: inputFont)
660
1105
  if let action {
661
1106
  let button = PCAccessoryButton(type: .system)
662
1107
  button.setImage(image.applyingSymbolConfiguration(config) ?? image, for: .normal)
@@ -664,19 +1109,25 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
664
1109
  button.action = action
665
1110
  button.accessibilityLabel = passwordToggle && trailingImage == nil
666
1111
  ? (revealed ? "Hide password" : "Show password")
667
- : nil
1112
+ : (spokenLabel.isEmpty ? nil : spokenLabel)
1113
+ button.accessibilityIdentifier = testID.isEmpty ? nil : testID
668
1114
  stack.addArrangedSubview(button)
669
1115
  } else {
670
1116
  let imageView = UIImageView(image: image.applyingSymbolConfiguration(config) ?? image)
671
1117
  imageView.tintColor = tinted ? .secondaryLabel : nil
672
1118
  imageView.contentMode = .scaleAspectFit
1119
+ imageView.accessibilityIdentifier = testID.isEmpty ? nil : testID
1120
+ if !spokenLabel.isEmpty {
1121
+ imageView.isAccessibilityElement = true
1122
+ imageView.accessibilityLabel = spokenLabel
1123
+ }
673
1124
  stack.addArrangedSubview(imageView)
674
1125
  }
675
1126
  }
676
1127
  if !text.isEmpty {
677
1128
  let label = UILabel()
678
1129
  label.text = text
679
- label.font = textFont ?? .preferredFont(forTextStyle: .body)
1130
+ label.font = inputFont
680
1131
  label.textColor = .secondaryLabel
681
1132
  label.adjustsFontForContentSizeCategory = true
682
1133
  stack.addArrangedSubview(label)
@@ -718,6 +1169,7 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
718
1169
  super.traitCollectionDidChange(previousTraitCollection)
719
1170
  if previousTraitCollection?.preferredContentSizeCategory != traitCollection.preferredContentSizeCategory {
720
1171
  applyFonts()
1172
+ applyAccessories()
721
1173
  }
722
1174
  if previousTraitCollection?.hasDifferentColorAppearance(comparedTo: traitCollection) == true {
723
1175
  applyColors()
@@ -731,6 +1183,8 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
731
1183
  var inputHeight: CGFloat
732
1184
  var footerHeight: CGFloat
733
1185
  var leading: Bool
1186
+ /// A multi-line field past maxLines scrolls its text
1187
+ var inputScrolls: Bool
734
1188
  var total: CGFloat {
735
1189
  var height = leading ? max(inputHeight, captionHeight) : inputHeight
736
1190
  if captionHeight > 0 && !leading { height += captionHeight + 6 }
@@ -748,10 +1202,21 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
748
1202
  let captionHeight = label.isEmpty ? 0 : ceil(captionLabel.font.lineHeight)
749
1203
  let fieldWidth = max(1, width - fieldOffset())
750
1204
 
751
- let inputHeight: CGFloat
1205
+ var inputHeight: CGFloat
1206
+ var inputScrolls = false
752
1207
  if multiline {
753
- let fitted = textView.sizeThatFits(CGSize(width: fieldWidth, height: .greatestFiniteMagnitude))
754
- inputHeight = max(singleLineMinHeight, ceil(fitted.height))
1208
+ let fitted = ceil(textView.sizeThatFits(CGSize(width: fieldWidth, height: .greatestFiniteMagnitude)).height)
1209
+ inputHeight = max(singleLineMinHeight, fitted)
1210
+ let lineHeight = inputFont.lineHeight
1211
+ let chrome = textViewInsets.top + textViewInsets.bottom
1212
+ if minLines > 0 {
1213
+ inputHeight = max(inputHeight, ceil(lineHeight * CGFloat(minLines) + chrome))
1214
+ }
1215
+ if maxLines > 0 {
1216
+ let cap = max(singleLineMinHeight, ceil(lineHeight * CGFloat(maxLines) + chrome))
1217
+ inputScrolls = fitted > cap
1218
+ inputHeight = min(inputHeight, cap)
1219
+ }
755
1220
  } else {
756
1221
  let fitted = textField.sizeThatFits(CGSize(width: fieldWidth, height: .greatestFiniteMagnitude))
757
1222
  inputHeight = max(singleLineMinHeight, ceil(fitted.height))
@@ -769,7 +1234,13 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
769
1234
  let counterHeight = counterLabel.isHidden ? 0 : ceil(counterLabel.font.lineHeight)
770
1235
  footerHeight = max(messageHeight, counterHeight)
771
1236
  }
772
- return Metrics(captionHeight: captionHeight, inputHeight: inputHeight, footerHeight: footerHeight, leading: leadingLabel)
1237
+ return Metrics(
1238
+ captionHeight: captionHeight,
1239
+ inputHeight: inputHeight,
1240
+ footerHeight: footerHeight,
1241
+ leading: leadingLabel,
1242
+ inputScrolls: inputScrolls
1243
+ )
773
1244
  }
774
1245
 
775
1246
  public override func layoutSubviews() {
@@ -779,6 +1250,9 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
779
1250
  let x = fieldOffset()
780
1251
  let fieldWidth = max(0, width - x)
781
1252
  var y: CGFloat = 0
1253
+ if multiline, textView.isScrollEnabled != m.inputScrolls {
1254
+ textView.isScrollEnabled = m.inputScrolls
1255
+ }
782
1256
 
783
1257
  if m.leading {
784
1258
  // Label column and field side by side, centred on the taller one
@@ -874,28 +1348,59 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
874
1348
  }
875
1349
  }
876
1350
 
1351
+ /// React Native autoComplete values onto text content types, as the
1352
+ /// core TextInput maps them, plus the names it leaves out. Values with no
1353
+ /// iOS equivalent, and unknown ones, give no content type.
877
1354
  private static func contentType(_ value: String) -> UITextContentType? {
878
1355
  switch value {
879
- case "username": return .username
880
- case "password": return .password
881
- case "new-password": return .newPassword
882
- case "one-time-code": return .oneTimeCode
1356
+ case "username", "username-new": return .username
1357
+ case "password", "current-password": return .password
1358
+ case "new-password", "password-new": return .newPassword
1359
+ case "one-time-code", "sms-otp", "email-otp", "2fa-app-otp": return .oneTimeCode
883
1360
  case "email": return .emailAddress
884
1361
  case "name": return .name
885
- case "given-name": return .givenName
886
- case "family-name": return .familyName
887
- case "tel": return .telephoneNumber
888
- case "street-address": return .fullStreetAddress
1362
+ case "given-name", "name-given": return .givenName
1363
+ case "family-name", "name-family": return .familyName
1364
+ case "additional-name", "name-middle": return .middleName
1365
+ case "honorific-prefix", "name-prefix": return .namePrefix
1366
+ case "honorific-suffix", "name-suffix": return .nameSuffix
1367
+ case "nickname": return .nickname
1368
+ case "organization": return .organizationName
1369
+ case "organization-title": return .jobTitle
1370
+ case "tel", "tel-national", "tel-device": return .telephoneNumber
1371
+ case "street-address", "postal-address": return .fullStreetAddress
1372
+ case "address-line1": return .streetAddressLine1
1373
+ case "address-line2", "postal-address-extended": return .streetAddressLine2
1374
+ case "postal-address-locality": return .addressCity
1375
+ case "postal-address-region": return .addressState
1376
+ case "postal-address-dependent-locality": return .sublocality
889
1377
  case "postal-code": return .postalCode
890
- case "country": return .countryName
1378
+ case "country", "postal-address-country": return .countryName
891
1379
  case "cc-number": return .creditCardNumber
892
- case "cc-exp":
893
- if #available(iOS 17.0, *) { return .creditCardExpiration }
894
- return nil
895
- case "cc-csc":
896
- if #available(iOS 17.0, *) { return .creditCardSecurityCode }
897
- return nil
898
1380
  case "url": return .URL
1381
+ case "flight-number": return .flightNumber
1382
+ default:
1383
+ if #available(iOS 17.0, *) { return contentType17(value) }
1384
+ return nil
1385
+ }
1386
+ }
1387
+
1388
+ @available(iOS 17.0, *)
1389
+ private static func contentType17(_ value: String) -> UITextContentType? {
1390
+ switch value {
1391
+ case "cc-exp": return .creditCardExpiration
1392
+ case "cc-exp-month": return .creditCardExpirationMonth
1393
+ case "cc-exp-year": return .creditCardExpirationYear
1394
+ case "cc-csc": return .creditCardSecurityCode
1395
+ case "cc-name": return .creditCardName
1396
+ case "cc-given-name": return .creditCardGivenName
1397
+ case "cc-middle-name": return .creditCardMiddleName
1398
+ case "cc-family-name": return .creditCardFamilyName
1399
+ case "cc-type": return .creditCardType
1400
+ case "birthdate-full": return .birthdate
1401
+ case "birthdate-day": return .birthdateDay
1402
+ case "birthdate-month": return .birthdateMonth
1403
+ case "birthdate-year": return .birthdateYear
899
1404
  default: return nil
900
1405
  }
901
1406
  }
@@ -955,10 +1460,51 @@ public final class PCTextFieldView: UIView, UITextFieldDelegate, UITextViewDeleg
955
1460
 
956
1461
  // MARK: - Helpers
957
1462
 
1463
+ /// The single-line input: insets for the box the field view draws, and
1464
+ /// VoiceOver activation for a field acting as a button.
1465
+ final class PCInputTextField: UITextField {
1466
+ var textInsets: UIEdgeInsets = .zero {
1467
+ didSet { if oldValue != textInsets { setNeedsLayout() } }
1468
+ }
1469
+
1470
+ /// Returns true when it handled the activation
1471
+ var onActivate: (() -> Bool)?
1472
+
1473
+ private func inset(_ rect: CGRect) -> CGRect {
1474
+ // Accessory views bring their own margins
1475
+ let left = leftView == nil ? textInsets.left : 0
1476
+ let right = rightView == nil ? textInsets.right : 0
1477
+ return rect.inset(by: UIEdgeInsets(top: textInsets.top, left: left, bottom: textInsets.bottom, right: right))
1478
+ }
1479
+
1480
+ override func textRect(forBounds bounds: CGRect) -> CGRect {
1481
+ inset(super.textRect(forBounds: bounds))
1482
+ }
1483
+
1484
+ override func editingRect(forBounds bounds: CGRect) -> CGRect {
1485
+ inset(super.editingRect(forBounds: bounds))
1486
+ }
1487
+
1488
+ override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
1489
+ inset(super.placeholderRect(forBounds: bounds))
1490
+ }
1491
+
1492
+ override func accessibilityActivate() -> Bool {
1493
+ onActivate?() == true || super.accessibilityActivate()
1494
+ }
1495
+ }
1496
+
958
1497
  /// A `UITextView` with a placeholder, drawn where the text starts.
959
1498
  final class PCPlaceholderTextView: UITextView {
960
1499
  let placeholderLabel = UILabel()
961
1500
 
1501
+ /// Returns true when it handled the activation
1502
+ var onActivate: (() -> Bool)?
1503
+
1504
+ override func accessibilityActivate() -> Bool {
1505
+ onActivate?() == true || super.accessibilityActivate()
1506
+ }
1507
+
962
1508
  var placeholder: String = "" {
963
1509
  didSet {
964
1510
  placeholderLabel.text = placeholder