@momo-kits/native-kits 0.300.3 → 0.300.4-debug

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 (272) hide show
  1. package/CLAUDE.md +51 -7
  2. package/README.md +2 -2
  3. package/build.gradle.kts +13 -0
  4. package/compose/build.gradle.kts +197 -0
  5. package/compose/build.gradle.kts.backup +197 -0
  6. package/compose/compose.podspec +47 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +20 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
  10. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  11. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +153 -0
  12. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +110 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +1071 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +65 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +81 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +115 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +542 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +49 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +86 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +189 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +478 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +265 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +235 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +223 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +265 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +185 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +361 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +108 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +308 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +330 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +220 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +207 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +92 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +560 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +125 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +207 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +501 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +176 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +236 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +180 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +325 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +17 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +194 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +287 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +54 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +50 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +41 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +338 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +137 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +116 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +215 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +404 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +732 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +40 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +233 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +217 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +455 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +103 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +74 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +413 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +39 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +363 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +37 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +133 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +41 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +135 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +74 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +118 -0
  124. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +44 -0
  125. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +62 -0
  126. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +42 -0
  127. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
  128. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +36 -0
  129. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
  130. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  131. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +217 -0
  132. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
  133. package/gradle/libs.versions.toml +68 -0
  134. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  135. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  136. package/gradle.properties +26 -0
  137. package/gradlew +252 -0
  138. package/gradlew.bat +94 -0
  139. package/ios/Application/ApplicationEnvironment.swift +24 -6
  140. package/ios/Application/DesignSystemKits.swift +100 -0
  141. package/ios/Application/DesignSystemKitsPayload.swift +68 -0
  142. package/ios/Application/MaxApi.swift +30 -3
  143. package/ios/Avatar/Avatar.swift +4 -3
  144. package/ios/Badge/Badge.swift +6 -4
  145. package/ios/Badge/BadgeDot.swift +1 -1
  146. package/ios/Badge/BadgeRibbon.swift +8 -2
  147. package/ios/Button/Button.swift +15 -12
  148. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
  149. package/ios/Carousel/Carousel.swift +29 -12
  150. package/ios/Checkbox/Checkbox.swift +7 -3
  151. package/ios/Chip/Chip.swift +5 -4
  152. package/ios/Collapse/Collapse.swift +70 -19
  153. package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
  154. package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
  155. package/ios/DateTimePicker/DateTimePicker.swift +54 -20
  156. package/ios/DateTimePicker/WheelPicker.swift +103 -105
  157. package/ios/Divider/Divider.swift +37 -4
  158. package/ios/Extensions/Color++.swift +38 -9
  159. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  160. package/ios/Extensions/SkeletonUICompat.swift +11 -0
  161. package/ios/Icon/Icon.swift +21 -23
  162. package/ios/Icon/Icons.swift +1329 -0
  163. package/ios/IconButton/IconButton.swift +15 -7
  164. package/ios/Image/Image.swift +7 -23
  165. package/ios/Information/Information.swift +27 -9
  166. package/ios/Input/ErrorView.swift +2 -2
  167. package/ios/Input/Input.swift +171 -148
  168. package/ios/Input/InputPhoneNumber.swift +10 -9
  169. package/ios/Input/InputSearch.swift +107 -78
  170. package/ios/Input/InputTextArea.swift +133 -148
  171. package/ios/InputDropDown/InputDropDown.swift +10 -7
  172. package/ios/InputMoney/InputMoney.swift +32 -11
  173. package/ios/InputOTP/InputOTP.swift +22 -12
  174. package/ios/Layout/Card.swift +50 -0
  175. package/ios/Layout/GridContext.swift +187 -3
  176. package/ios/Layout/Item.swift +5 -4
  177. package/ios/Layout/ScreenLayout.swift +415 -0
  178. package/ios/Layout/Section.swift +4 -100
  179. package/ios/Loader/Loader.swift +5 -9
  180. package/ios/Lottie/LottieView.swift +14 -2
  181. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
  182. package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
  183. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
  184. package/ios/Navigation/BottomTab/DuoBarCompression.swift +104 -0
  185. package/ios/Navigation/BottomTab/DuoTabBar.swift +329 -0
  186. package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
  187. package/ios/Navigation/Component/Header.swift +545 -0
  188. package/ios/Navigation/Component/HeaderBackground.swift +154 -0
  189. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  190. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  191. package/ios/Navigation/Component/HeaderRight.swift +425 -0
  192. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  193. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
  194. package/ios/Navigation/Component/SideBar.swift +183 -0
  195. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
  196. package/ios/Navigation/HostBackGestureBridge.swift +78 -0
  197. package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
  198. package/ios/Navigation/Navigation.swift +204 -0
  199. package/ios/Navigation/NavigationContainer.swift +195 -0
  200. package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
  201. package/ios/Navigation/StackScreen.swift +930 -0
  202. package/ios/Navigation/StackTransition.swift +400 -0
  203. package/ios/Navigation/SwipeBackHaptic.swift +33 -0
  204. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  205. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  206. package/ios/Pagination/PaginationScroll.swift +2 -2
  207. package/ios/Popup/PopupDisplay.swift +72 -135
  208. package/ios/Popup/PopupInput.swift +10 -13
  209. package/ios/Popup/PopupLayout.swift +138 -0
  210. package/ios/Popup/PopupNotify.swift +59 -69
  211. package/ios/Popup/PopupPromotion.swift +31 -47
  212. package/ios/PopupView/FullscreenPopup.swift +13 -19
  213. package/ios/PopupView/PopupView.swift +1 -1
  214. package/ios/PopupView/Utils++.swift +0 -8
  215. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  216. package/ios/Radio/Radio.swift +2 -1
  217. package/ios/Rating/Rating.swift +9 -1
  218. package/ios/Resources/dot_loading.json +558 -0
  219. package/ios/Resources/lottie_circle_loader.json +1 -0
  220. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  221. package/ios/Skeleton/Skeleton.swift +22 -27
  222. package/ios/Slider/Slider.swift +16 -18
  223. package/ios/Stepper/Stepper.swift +7 -3
  224. package/ios/Steps/Steps.swift +8 -6
  225. package/ios/SuggestAction/SuggestAction.swift +5 -0
  226. package/ios/Swipeable/SwipeCell.swift +10 -6
  227. package/ios/Switch/Switch.swift +22 -20
  228. package/ios/TabView/TabView.swift +102 -21
  229. package/ios/Tag/Tag.swift +15 -4
  230. package/ios/Template/Logo/Logo.swift +2 -1
  231. package/ios/Template/TrustBanner/TrustBanner.swift +109 -55
  232. package/ios/Title/Title.swift +6 -17
  233. package/ios/Tooltip/Tooltip.swift +29 -14
  234. package/ios/Typography/FontScale.swift +17 -14
  235. package/ios/Typography/Text.swift +156 -20
  236. package/ios/Typography/Typography.swift +2 -2
  237. package/ios/Uploader/Uploader.swift +3 -1
  238. package/ios/native-kits.podspec +2 -3
  239. package/ios-demo/MoMoUIKitsDemo.podspec +1 -3
  240. package/ios-demo/README.md +120 -0
  241. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  242. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
  243. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
  244. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
  245. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  246. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
  247. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
  248. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
  249. package/package.json +1 -1
  250. package/publish.sh +19 -2
  251. package/scripts/gen-ios-icons.mjs +36 -0
  252. package/settings.gradle.kts +64 -0
  253. package/ios/Application/Components.swift +0 -270
  254. package/ios/Application/ComposeApi.swift +0 -22
  255. package/ios/Application/HeaderRight.swift +0 -306
  256. package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
  257. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  258. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  259. package/ios/Application/Navigation/NavigationContainer.swift +0 -213
  260. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  261. package/ios/Application/Navigation/NavigatorManager.swift +0 -22
  262. package/ios/Application/Navigation/component/Header.swift +0 -178
  263. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
  264. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  265. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  266. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  267. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
  268. package/ios/Application/Screen.swift +0 -250
  269. package/ios/Application/StackScreen.swift +0 -492
  270. package/ios/Typography/FontScaleStore.swift +0 -58
  271. package/ios/Typography/KitDesignSystem.swift +0 -17
  272. /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
@@ -28,7 +28,7 @@ public enum IconSize {
28
28
 
29
29
  // MARK: - IconShape
30
30
 
31
- public enum IconShape {
31
+ public enum IconShape: Sendable {
32
32
  case circle
33
33
  case square
34
34
  }
@@ -68,8 +68,7 @@ public struct IconButton: View {
68
68
  public var body: some View {
69
69
  let bg = backgroundColor
70
70
  let fg = contentColor
71
- // OUTLINE allows an override color for its border, mirroring Compose `color ?? theme.colors.primary`.
72
- let border: Color? = type == .outline ? (color ?? theme.colors.primary) : borderColor
71
+ let border = borderColor
73
72
  let borderWidth = type.borderWidth
74
73
 
75
74
  SwiftUI.Button(action: {
@@ -82,9 +81,11 @@ public struct IconButton: View {
82
81
  .background(bg)
83
82
  .clipShape(IconButtonShape(shape))
84
83
  .overlay(
84
+ // Inside the bounds, like Compose `border`.
85
85
  IconButtonShape(shape)
86
- .stroke(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
86
+ .strokeBorder(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
87
87
  )
88
+ .contentShape(IconButtonShape(shape))
88
89
  }
89
90
  .buttonStyle(.plain)
90
91
  .disabled(!isEnabled)
@@ -122,8 +123,9 @@ public struct IconButton: View {
122
123
  /// Type-erased shape switch (`Circle` vs a fixed `Radius.S` rounded rect) — mirrors
123
124
  /// Compose's `IconShape.toShape()`. `AnyShape` needs iOS 17+, so this hand-rolls the same
124
125
  /// erasure at the deployment target's iOS 15 floor.
125
- private struct IconButtonShape: Shape {
126
+ private struct IconButtonShape: InsettableShape {
126
127
  let shape: IconShape
128
+ var insetAmount: CGFloat = 0
127
129
 
128
130
  init(_ shape: IconShape) {
129
131
  self.shape = shape
@@ -132,9 +134,15 @@ private struct IconButtonShape: Shape {
132
134
  func path(in rect: CGRect) -> Path {
133
135
  switch shape {
134
136
  case .circle:
135
- return Circle().path(in: rect)
137
+ return Circle().inset(by: insetAmount).path(in: rect)
136
138
  case .square:
137
- return RoundedRectangle(cornerRadius: Radius.S).path(in: rect)
139
+ return RoundedRectangle(cornerRadius: Radius.S).inset(by: insetAmount).path(in: rect)
138
140
  }
139
141
  }
142
+
143
+ func inset(by amount: CGFloat) -> IconButtonShape {
144
+ var shape = self
145
+ shape.insetAmount += amount
146
+ return shape
147
+ }
140
148
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  import Foundation
4
4
  import SDWebImageSwiftUI
5
- import SkeletonUI
6
5
  import SwiftUI
7
6
 
8
7
  public enum ImageState {
@@ -39,26 +38,23 @@ public struct ImageView: View {
39
38
  .resizable()
40
39
  .renderingMode(color != nil ? .template : .original)
41
40
  .placeholder {
42
- VStack(alignment: .center, content: {
41
+ VStack(alignment: .center) {
43
42
  if error {
44
- Image("media_fail")
45
- .resizable()
46
- .renderingMode(.template)
47
- .foregroundColor(theme.colors.text.disable)
48
- .frame(width: 24, height: 24)
49
- } else if placeholder != nil {
43
+ Icon(source: "media_fail", color: theme.colors.text.disable)
44
+ } else if let placeholder {
50
45
  placeholder
46
+ } else if loading {
47
+ Skeleton()
51
48
  }
52
- })
49
+ }
53
50
  .frame(maxWidth: .infinity, maxHeight: .infinity)
54
- .skeleton(with: loading && !error && placeholder == nil)
55
- .shape(type: .rectangle)
56
51
  }
57
52
  .scaledToFit()
58
53
  .transition(.fade)
59
54
  .aspectRatio(aspectRatio, contentMode: contentMode)
60
55
  .foregroundColor(color)
61
56
  .onAppear { onStateChanged?(.loading) }
57
+ .onChange(of: url) { _ in error = false }
62
58
  }
63
59
 
64
60
  // MARK: Internal
@@ -73,15 +69,3 @@ public struct ImageView: View {
73
69
  @State var error: Bool = false
74
70
  var url: String
75
71
  }
76
-
77
- private struct ColorMultiplyModifier: ViewModifier {
78
- let color: Color?
79
-
80
- func body(content: Content) -> some View {
81
- if let color {
82
- content.colorMultiply(color)
83
- } else {
84
- content
85
- }
86
- }
87
- }
@@ -19,6 +19,7 @@ public struct Information: View {
19
19
  private let showIconClose: Bool
20
20
  private let onPressClose: (() -> Void)?
21
21
  private let highlightText: [String]
22
+ private let accessibilityLabel: String?
22
23
 
23
24
  public init(
24
25
  title: String? = nil,
@@ -30,7 +31,8 @@ public struct Information: View {
30
31
  action: String? = nil,
31
32
  showIconClose: Bool = true,
32
33
  onPressClose: (() -> Void)? = nil,
33
- highlightText: [String] = []
34
+ highlightText: [String] = [],
35
+ accessibilityLabel: String? = nil
34
36
  ) {
35
37
  self.title = title
36
38
  self.description = description
@@ -42,6 +44,11 @@ public struct Information: View {
42
44
  self.showIconClose = showIconClose
43
45
  self.onPressClose = onPressClose
44
46
  self.highlightText = highlightText
47
+ self.accessibilityLabel = accessibilityLabel
48
+ }
49
+
50
+ private var automationId: String {
51
+ accessibilityLabel ?? "Information\(title.map { "/\($0)" } ?? "")"
45
52
  }
46
53
 
47
54
  private var borderColor: Color {
@@ -80,20 +87,23 @@ public struct Information: View {
80
87
  VStack(alignment: .leading, spacing: 0) {
81
88
  HStack(alignment: .top, spacing: 0) {
82
89
  if let image {
83
- ImageView(image)
90
+ ImageView(image, contentMode: .fill)
84
91
  .frame(width: 40, height: 40)
85
92
  .clipShape(RoundedRectangle(cornerRadius: Radius.S))
93
+ .accessibilityIdentifier("\(automationId)|img")
86
94
  .padding(.trailing, Spacing.S)
87
95
  } else if showIcon {
88
96
  Icon(source: iconSource, size: 16, color: accentColor)
97
+ .accessibilityIdentifier("\(automationId)|icon")
89
98
  .padding(.trailing, Spacing.S)
90
99
  }
91
100
 
92
101
  VStack(alignment: .leading, spacing: 0) {
93
102
  if let title {
94
- MomoText(title, typography: .labelDefaultMedium)
103
+ MomoText(title, typography: .labelDefaultMedium, accessibilityId: "\(automationId)|title-text")
95
104
  }
96
105
  MomoText(description, typography: .descriptionDefaultRegular, highlightText: highlightText)
106
+ .accessibilityIdentifier("\(automationId)|description-text")
97
107
  }
98
108
  .frame(maxWidth: .infinity, alignment: .leading)
99
109
 
@@ -101,15 +111,21 @@ public struct Information: View {
101
111
  Icon(source: "navigation_close", size: 16, color: theme.colors.text.default)
102
112
  .contentShape(Rectangle())
103
113
  .onTapGesture(perform: onPressClose)
114
+ .accessibilityAddTraits(.isButton)
115
+ .accessibilityIdentifier("\(automationId)|close-icon-touch")
104
116
  }
105
117
  }
106
118
 
107
119
  if let action, let onPressAction {
108
- MomoText(action, typography: .actionXsBold, color: accentColor)
109
- .frame(maxWidth: .infinity, alignment: .trailing)
110
- .contentShape(Rectangle())
111
- .onTapGesture(perform: onPressAction)
112
- .padding(.top, Spacing.S)
120
+ HStack(spacing: 0) {
121
+ MomoText(action, typography: .actionXsBold, color: accentColor, accessibilityId: "\(automationId)|cta-text")
122
+ }
123
+ .frame(maxWidth: .infinity, alignment: .trailing)
124
+ .contentShape(Rectangle())
125
+ .onTapGesture(perform: onPressAction)
126
+ .accessibilityElement(children: .contain)
127
+ .accessibilityIdentifier("\(automationId)|cta-touch")
128
+ .padding(.top, Spacing.S)
113
129
  }
114
130
  }
115
131
  .padding(Spacing.M)
@@ -119,8 +135,10 @@ public struct Information: View {
119
135
  )
120
136
  .overlay(
121
137
  RoundedRectangle(cornerRadius: Radius.S)
122
- .stroke(borderColor, lineWidth: 0.5)
138
+ .strokeBorder(borderColor, lineWidth: 0.5)
123
139
  )
140
+ .accessibilityElement(children: .contain)
141
+ .accessibilityIdentifier(automationId)
124
142
  }
125
143
  }
126
144
 
@@ -37,9 +37,9 @@ public struct ErrorView: View {
37
37
  }
38
38
  .padding(.top, Spacing.XS)
39
39
  } else if errorSpacing {
40
- Spacer()
41
- .padding(.top, Spacing.XS)
40
+ Color.clear
42
41
  .frame(height: 18)
42
+ .padding(.top, Spacing.XS)
43
43
  }
44
44
  }
45
45
 
@@ -37,8 +37,7 @@ public struct Input: View {
37
37
  @State private var isFocused: Bool = false
38
38
  @State private var isPasswordHidden: Bool = true
39
39
  @FocusState private var isFieldFocused: Bool
40
- @State private var floatingLabelHeight: CGFloat = 16
41
-
40
+
42
41
  public init(
43
42
  text: Binding<String>,
44
43
  placeholder: String = "",
@@ -112,142 +111,115 @@ public struct Input: View {
112
111
  }
113
112
  )
114
113
 
115
- VStack(alignment: .leading, spacing: 4) {
116
- ZStack(alignment: .topLeading) {
117
- // Floating label
118
- if !floatingValue.isEmpty || !floatingIcon.isEmpty {
119
- HStack(spacing: Spacing.XS) {
120
- MomoText(floatingValue, typography: .labelSMedium, color: getFloatingColor())
121
- if required {
122
- MomoText("*", typography: .labelSMedium, color: theme.colors.error.primary)
123
- }
124
- if !floatingIcon.isEmpty {
125
- Icon(source: floatingIcon, size: 16, color: getFloatingIconColor())
126
- }
127
- }
128
- .padding(.horizontal, Spacing.S)
129
- .background(
130
- GeometryReader { geo in
131
- Color.clear.onAppear { floatingLabelHeight = geo.size.height }
132
- }
133
- )
134
- .background(theme.colors.background.surface)
135
- // Kotlin centers the label within the (box-height-tall) decorationBox
136
- // by default, then applies `offsetY = -size.values.height / 2` — net
137
- // effect: the label ends up centered exactly on the box's top border
138
- // (spanning -labelHeight/2 ... +labelHeight/2 around y=0). Our ZStack
139
- // anchors top-leading instead, so reproducing that requires offsetting
140
- // by half the label's OWN measured height, not half the box height.
141
- .offset(x: Spacing.S, y: -floatingLabelHeight / 2)
142
- .zIndex(10)
114
+ VStack(alignment: .leading, spacing: 0) {
115
+ // Input container
116
+ HStack(alignment: .center, spacing: 0) {
117
+ // Leading icon
118
+ if !leadingIcon.isEmpty {
119
+ Icon(source: leadingIcon, size: size == .small ? 24 : 32, color: leadingIconColor ?? theme.colors.text.hint)
120
+ .padding(.trailing, Spacing.M)
143
121
  }
144
-
145
- // Input container
146
- HStack(alignment: .center, spacing: 0) {
147
- // Leading icon
148
- if !leadingIcon.isEmpty {
149
- Icon(source: leadingIcon, size: size == .small ? 24 : 32, color: getLeadingIconColor())
150
- .padding(.trailing, Spacing.M)
122
+
123
+ // Text input field
124
+ ZStack(alignment: .leading) {
125
+ if text.isEmpty {
126
+ MomoText(
127
+ placeholder + (required && floatingValue.isEmpty ? " *" : ""),
128
+ typography: fontWeight == .bold ? .actionSBold : .bodyDefaultRegular,
129
+ color: getPlaceholderColor()
130
+ )
151
131
  }
152
-
153
- // Text input field
154
- ZStack(alignment: .leading) {
155
- if text.isEmpty {
156
- MomoText(
157
- placeholder + (required && floatingValue.isEmpty ? " *" : ""),
158
- typography: fontWeight == .bold ? .actionSBold : .bodyDefaultRegular,
159
- color: getPlaceholderColor()
160
- )
161
- }
162
-
163
- if secureTextEntry && isPasswordHidden {
164
- SecureInputField(
165
- text: $text,
166
- keyboardType: keyboardType,
167
- fontSize: fontScale(14),
168
- fontWeight: fontWeight == .bold ? .bold : .regular,
169
- textColor: UIColor(getTextColor()),
170
- cursorColor: UIColor(theme.colors.primary),
171
- isDisabled: disabled || readOnly,
172
- maxLength: maxLength,
173
- autofocus: autofocus,
174
- onFocusChange: { focused in
175
- handleFocusChange(focused)
176
- },
177
- onChangeText: onChangeText
178
- )
179
- } else {
180
- TextField("", text: textBinding, onEditingChanged: { focused in
132
+
133
+ // One UITextField for both states: swapping views on the eye toggle would drop focus.
134
+ if secureTextEntry {
135
+ SecureInputField(
136
+ text: $text,
137
+ isSecure: isPasswordHidden,
138
+ keyboardType: keyboardType,
139
+ fontSize: fontScale(14),
140
+ fontWeight: fontWeight == .bold ? .bold : .regular,
141
+ textColor: UIColor(getTextColor()),
142
+ cursorColor: UIColor(theme.colors.primary),
143
+ isDisabled: disabled || readOnly,
144
+ maxLength: maxLength,
145
+ autofocus: autofocus,
146
+ onFocusChange: { focused in
181
147
  handleFocusChange(focused)
182
- })
183
- .keyboardType(keyboardType)
184
- .font(fontWeight == .bold ? .action_s_bold : .body_default_regular)
185
- .ignoreBoldTextSetting()
186
- .foregroundColor(getTextColor())
187
- .disabled(disabled || readOnly)
188
- .applyCursorColor(theme.colors.primary)
189
- .focused($isFieldFocused)
190
- .onAppear {
191
- if autofocus { isFieldFocused = true }
192
- }
193
- // SwiftUI reads a TextField's UIKit input traits only when it
194
- // builds the backing UITextField, so a later `keyboardType`
195
- // change is silently dropped. The secure path can call
196
- // `reloadInputViews()` in `updateUIView`; here the only lever
197
- // is to force a rebuild.
198
- .id(keyboardType.rawValue)
148
+ },
149
+ onChangeText: onChangeText
150
+ )
151
+ } else {
152
+ TextField("", text: textBinding, onEditingChanged: { focused in
153
+ handleFocusChange(focused)
154
+ })
155
+ .keyboardType(keyboardType)
156
+ .kitFont(fontWeight == .bold ? .actionSBold : .bodyDefaultRegular)
157
+ .ignoreBoldTextSetting()
158
+ .foregroundColor(getTextColor())
159
+ .disabled(disabled || readOnly)
160
+ .applyCursorColor(theme.colors.primary)
161
+ .focused($isFieldFocused)
162
+ .onAppear {
163
+ if autofocus { isFieldFocused = true }
199
164
  }
165
+ // SwiftUI reads a TextField's UIKit input traits only when it
166
+ // builds the backing UITextField, so a later `keyboardType`
167
+ // change is silently dropped. The secure path can call
168
+ // `reloadInputViews()` in `updateUIView`; here the only lever
169
+ // is to force a rebuild.
170
+ .id(keyboardType.rawValue)
200
171
  }
201
-
202
- // Clear button (only show when focused and has text)
203
- if isFocused && !text.isEmpty {
204
- SwiftUI.Button(action: {
205
- text = ""
206
- onChangeText?("")
207
- }) {
208
- Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
209
- .padding(.leading, Spacing.S).accessibility(identifier: "ic_clear")
210
- }
172
+ }
173
+
174
+ // Clear button (only show when focused and has text)
175
+ if isFocused && !text.isEmpty {
176
+ SwiftUI.Button(action: {
177
+ text = ""
178
+ onChangeText?("")
179
+ }) {
180
+ Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
181
+ .padding(.horizontal, Spacing.XS)
211
182
  }
212
-
213
- // Loading indicator
214
- if loading {
215
- ActivityIndicator(isAnimating: .constant(true), style: .medium)
216
- .frame(width: 16, height: 16)
217
- .padding(.leading, Spacing.S)
183
+ .accessibilityIdentifier("ic_clear")
184
+ }
185
+
186
+ // Loading indicator
187
+ if loading {
188
+ ActivityIndicator(isAnimating: .constant(true), style: .medium, color: getRightIconColor())
189
+ .frame(width: 16, height: 16)
190
+ .padding(.leading, Spacing.S)
191
+ }
192
+
193
+ // Right icon: password toggle once there is text, otherwise the custom icon (as Compose)
194
+ if secureTextEntry && !text.isEmpty {
195
+ SwiftUI.Button(action: togglePasswordVisibility) {
196
+ Icon(
197
+ source: isPasswordHidden ? "24_security_eye_off" : "24_security_eye_open",
198
+ size: 24,
199
+ color: getRightIconColor()
200
+ )
201
+ .padding(.leading, Spacing.S)
218
202
  }
219
-
220
- // Right icon (password toggle or custom icon)
221
- if secureTextEntry {
222
- if !text.isEmpty {
223
- SwiftUI.Button(action: togglePasswordVisibility) {
224
- Icon(
225
- source: isPasswordHidden ? "24_security_eye_off" : "24_security_eye_open",
226
- size: 24,
227
- color: getRightIconColor()
228
- )
229
- .padding(.leading, Spacing.S).accessibility(identifier: "ic_show_hide")
230
- }
231
- }
232
- } else if !rightIcon.isEmpty {
233
- SwiftUI.Button(action: { onRightIconPressed?() }) {
234
- Icon(source: rightIcon, size: 24, color: getRightIconColor())
235
- .padding(.leading, Spacing.S)
236
- }
203
+ .accessibilityIdentifier("ic_show_hide")
204
+ } else if !rightIcon.isEmpty {
205
+ SwiftUI.Button(action: { onRightIconPressed?() }) {
206
+ Icon(source: rightIcon, size: 24, color: getRightIconColor())
207
+ .padding(.leading, Spacing.S)
237
208
  }
238
209
  }
239
- .padding(.horizontal, Spacing.M)
240
- .frame(height: fontScale(size == .small ? 48 : 56))
241
- .background(
242
- RoundedRectangle(cornerRadius: Radius.S)
243
- .fill(theme.colors.background.surface)
244
- )
245
- .overlay(
246
- RoundedRectangle(cornerRadius: Radius.S)
247
- .stroke(borderColor(), lineWidth: 1)
248
- )
249
210
  }
250
-
211
+ .padding(.horizontal, Spacing.M)
212
+ .frame(height: fontScale(size == .small ? 48 : 56))
213
+ .background(
214
+ RoundedRectangle(cornerRadius: Radius.S)
215
+ .fill(theme.colors.background.surface)
216
+ )
217
+ .overlay(
218
+ RoundedRectangle(cornerRadius: Radius.S)
219
+ .stroke(borderColor(), lineWidth: 1)
220
+ )
221
+ .overlay(alignment: .topLeading) { floatingLabel }
222
+
251
223
  // Error or hint
252
224
  ErrorView(
253
225
  errorMessage: error,
@@ -263,10 +235,33 @@ public struct Input: View {
263
235
  }
264
236
  }
265
237
  }
266
-
238
+
239
+ /// Centered on the box's top border, like Compose's `FloatingLabel` (offset by -height / 2).
240
+ @ViewBuilder
241
+ private var floatingLabel: some View {
242
+ if !floatingValue.isEmpty || !floatingIcon.isEmpty {
243
+ HStack(spacing: 0) {
244
+ MomoText(floatingValue, typography: .labelSMedium, color: getFloatingColor())
245
+ if required {
246
+ MomoText("*", typography: .labelSMedium, color: theme.colors.error.primary)
247
+ }
248
+ if !floatingIcon.isEmpty {
249
+ Icon(source: floatingIcon, size: 16, color: getFloatingIconColor())
250
+ .padding(.leading, Spacing.XS)
251
+ }
252
+ }
253
+ .padding(.horizontal, Spacing.S)
254
+ .background(theme.colors.background.surface)
255
+ .alignmentGuide(.top) { $0[VerticalAlignment.center] }
256
+ .offset(x: Spacing.S)
257
+ }
258
+ }
259
+
267
260
  // MARK: - Helpers
268
-
261
+
269
262
  private func handleFocusChange(_ focused: Bool) {
263
+ // The keyboard-hide fallback can report a blur the field already reported.
264
+ guard focused != isFocused else { return }
270
265
  isFocused = focused
271
266
  if focused {
272
267
  onFocus?()
@@ -316,10 +311,6 @@ public struct Input: View {
316
311
  return disabled ? theme.colors.text.disable : (floatingIconColor ?? theme.colors.text.default)
317
312
  }
318
313
 
319
- private func getLeadingIconColor() -> Color {
320
- return disabled ? theme.colors.text.disable : (leadingIconColor ?? theme.colors.text.hint)
321
- }
322
-
323
314
  private func getRightIconColor() -> Color {
324
315
  return disabled ? theme.colors.text.disable : (rightIconColor ?? theme.colors.text.default)
325
316
  }
@@ -329,6 +320,7 @@ public struct Input: View {
329
320
  // MARK: - Secure Input Field (UIViewRepresentable)
330
321
  private struct SecureInputField: UIViewRepresentable {
331
322
  @Binding var text: String
323
+ var isSecure: Bool
332
324
  var keyboardType: UIKeyboardType
333
325
  var fontSize: CGFloat
334
326
  var fontWeight: UIFont.Weight
@@ -346,10 +338,19 @@ private struct SecureInputField: UIViewRepresentable {
346
338
 
347
339
  func makeUIView(context: Context) -> UITextField {
348
340
  let textField = UITextField()
349
- textField.isSecureTextEntry = true
341
+ textField.isSecureTextEntry = isSecure
342
+ // Stays a password field while revealed: keep it out of autocorrect / QuickType learning.
343
+ textField.autocorrectionType = .no
344
+ textField.autocapitalizationType = .none
345
+ textField.spellCheckingType = .no
350
346
  textField.delegate = context.coordinator
351
347
  textField.keyboardType = keyboardType
352
- textField.font = UIFont.systemFont(ofSize: fontSize, weight: fontWeight)
348
+ textField.font = momoUIFont(
349
+ size: fontSize,
350
+ weight: fontWeight,
351
+ family: context.environment.appTheme.font,
352
+ isWhiteList: context.environment.designSystemKitsConfigs.isWhiteList
353
+ )
353
354
  textField.textColor = textColor
354
355
  textField.isEnabled = !isDisabled
355
356
  textField.setContentHuggingPriority(.defaultLow, for: .horizontal)
@@ -374,6 +375,15 @@ private struct SecureInputField: UIViewRepresentable {
374
375
  if textField.text != text {
375
376
  textField.text = text
376
377
  }
378
+ let font = momoUIFont(
379
+ size: fontSize,
380
+ weight: fontWeight,
381
+ family: context.environment.appTheme.font,
382
+ isWhiteList: context.environment.designSystemKitsConfigs.isWhiteList
383
+ )
384
+ if textField.font != font {
385
+ textField.font = font
386
+ }
377
387
  textField.isEnabled = !isDisabled
378
388
  textField.textColor = textColor
379
389
  textField.tintColor = cursorColor
@@ -381,6 +391,12 @@ private struct SecureInputField: UIViewRepresentable {
381
391
  textField.keyboardType = keyboardType
382
392
  textField.reloadInputViews()
383
393
  }
394
+ if textField.isSecureTextEntry != isSecure {
395
+ textField.isSecureTextEntry = isSecure
396
+ if textField.isFirstResponder {
397
+ context.coordinator.reinsertText(in: textField)
398
+ }
399
+ }
384
400
  }
385
401
 
386
402
  class Coordinator: NSObject, UITextFieldDelegate {
@@ -391,19 +407,21 @@ private struct SecureInputField: UIViewRepresentable {
391
407
  self.parent = parent
392
408
  }
393
409
 
394
- func textFieldDidBeginEditing(_ textField: UITextField) {
410
+ // When a secure text field regains focus (or turns secure while editing),
411
+ // iOS marks the existing text as "committed" and the next keystroke would
412
+ // replace ALL of it. Re-inserting via insertText() puts it back into an
413
+ // "active editing" state (and moves the caret to the end).
414
+ func reinsertText(in textField: UITextField) {
395
415
  let existingText = textField.text ?? ""
396
- if !existingText.isEmpty {
397
- // When a secure text field regains focus, iOS marks the
398
- // existing text as "committed". The next keystroke would
399
- // replace ALL committed text with the new character.
400
- // Prevent this by clearing and re-inserting via insertText(),
401
- // which puts the text into an "active editing" state.
402
- isResettingText = true
403
- textField.text = ""
404
- textField.insertText(existingText)
405
- isResettingText = false
406
- }
416
+ guard !existingText.isEmpty else { return }
417
+ isResettingText = true
418
+ textField.text = ""
419
+ textField.insertText(existingText)
420
+ isResettingText = false
421
+ }
422
+
423
+ func textFieldDidBeginEditing(_ textField: UITextField) {
424
+ reinsertText(in: textField)
407
425
  parent.onFocusChange(true)
408
426
  }
409
427
 
@@ -460,12 +478,17 @@ private extension View {
460
478
  struct ActivityIndicator: UIViewRepresentable {
461
479
  @Binding var isAnimating: Bool
462
480
  let style: UIActivityIndicatorView.Style
481
+ /// Compose tints the spinner with the right-icon color; nil keeps the system gray.
482
+ var color: Color? = nil
463
483
 
464
484
  func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
465
485
  return UIActivityIndicatorView(style: style)
466
486
  }
467
487
 
468
488
  func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
489
+ if let color {
490
+ uiView.color = UIColor(color)
491
+ }
469
492
  isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
470
493
  }
471
494
  }