@momo-kits/native-kits 0.165.1-beta.10 → 0.165.2-beta.2

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 (152) hide show
  1. package/CLAUDE.md +1 -0
  2. package/ios/Application/Navigation/NavigatorManager.swift +0 -27
  3. package/ios/Information/Information.swift +5 -2
  4. package/ios/InputMoney/InputMoney.swift +76 -21
  5. package/ios/Typography/Text.swift +41 -1
  6. package/ios/native-kits.podspec +1 -1
  7. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  8. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +9 -1
  9. package/package.json +1 -1
  10. package/publish.sh +25 -0
  11. package/.codegraph/codegraph.db +0 -0
  12. package/.codegraph/daemon.log +0 -100
  13. package/build.gradle.kts +0 -11
  14. package/compose/build.gradle.kts +0 -190
  15. package/compose/build.gradle.kts.backup +0 -190
  16. package/compose/compose.podspec +0 -47
  17. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -19
  18. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
  19. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
  20. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
  21. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -150
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -124
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -831
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -56
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -82
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -356
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -113
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -76
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -134
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -475
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -216
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -240
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -264
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -328
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -97
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -310
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -323
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -199
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -91
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -552
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -88
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -498
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -234
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -188
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -16
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -188
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -296
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -52
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -38
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -39
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -299
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -129
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -106
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -207
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -393
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -609
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -35
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -175
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -225
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -331
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -81
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -106
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -75
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -309
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -37
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -364
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -132
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -127
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
  132. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
  133. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -69
  134. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
  135. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
  136. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
  137. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -105
  138. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -40
  139. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -98
  140. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -32
  141. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
  142. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
  143. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -217
  144. package/gradle/libs.versions.toml +0 -69
  145. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  146. package/gradle/wrapper/gradle-wrapper.properties +0 -8
  147. package/gradle.properties +0 -26
  148. package/gradlew +0 -252
  149. package/gradlew.bat +0 -94
  150. package/ios-demo/README.md +0 -120
  151. package/local.properties +0 -8
  152. package/settings.gradle.kts +0 -64
package/CLAUDE.md CHANGED
@@ -45,6 +45,7 @@ Publishing is automated (`publish.sh` + GitLab CI on `[ci build]` / `main`). Do
45
45
  - **Platform code** goes through `expect`/`actual` in `platform/` — never reference Android/iOS APIs from `commonMain` directly.
46
46
  - **Native bridge:** `IMaxApi` (`vn.momo.maxapi`, exposed as `LocalMaxApi`) is the host-app bridge (dismiss, tracking, device info, language).
47
47
  - Navigation uses a runtime `DynamicScreenRegistry`; overlays (modal/bottom-sheet/snackbar) go through `Navigator` + `OverplayComponentRegistry`.
48
+ - **iOS swipe-back:** the host owns the only edge-pan recogniser and replays it through `HostBackGestureBridge` (`navigation/HostBackGestureBridge.kt`; the kit framework is never exported, so the host reaches it through its own Koin-provided facade): `checkCanPop` / `canPopInner` / `pop` route the back into the kit stack, `started` / `progressed` / `completed` / `cancelled` seek the NavHost transition, `shouldUseFloatingArrowBack` is set by bottom tabs. `NavigationContainer` attaches itself automatically (`AttachHostBackGesture`, iOS only, detached by identity on dispose) — hosts need no wrapper and no `setNavigator`. `maxApi` is a required parameter of `NavigationContainer` — every container, nested or root, is handed its host bridge explicitly.
48
49
 
49
50
  ## i18n / Localize (port of RN `Localize`)
50
51
 
@@ -1,19 +1,3 @@
1
- //
2
- // NavigatorManager.swift
3
- // MoMoUIKits
4
- //
5
- // Host bridge for the native iOS edge-swipe back — the SwiftUI mirror of Compose's
6
- // `ComposeNavigatorManager` (momo-app `vn.momo.compose.uikits.ComposeNavigatorManager`).
7
- //
8
- // The kit does NOT own the swipe gesture. In the host app a kit screen lives inside a
9
- // `UIHostingController` hosted by the mini-app container VC, and that VC owns the
10
- // `UIScreenEdgePanGestureRecognizer`. When the swipe fires the host asks this manager
11
- // whether a kit stack is mounted (`checkCanPop`) and, if so, routes the back into it
12
- // (`pop` → `Navigator.onBackSafe`) instead of dismissing the whole mini-app. Standalone
13
- // (no such host, e.g. the demo app) there is no edge swipe — same as Compose on iOS,
14
- // whose `BackHandler` is a no-op and which relies entirely on the host.
15
- //
16
-
17
1
  import Foundation
18
2
 
19
3
  @available(iOS 16.0, *)
@@ -21,28 +5,17 @@ public final class NavigatorManager {
21
5
  public static let shared = NavigatorManager()
22
6
  private init() {}
23
7
 
24
- /// Stack of mounted navigators; the last one is the visible top (nested mini-apps
25
- /// push more than one). Held strongly, mirroring `ComposeNavigatorManager` — entries
26
- /// are released by `NavigationContainer` on disappear, not by ARC.
27
8
  private var navigators: [Navigator] = []
28
9
 
29
- /// Idempotent: SwiftUI can re-run a view's `onAppear`, and a double push would need a
30
- /// matching double remove to unwind.
31
10
  public func push(_ navigator: Navigator) {
32
11
  guard !navigators.contains(where: { $0 === navigator }) else { return }
33
12
  navigators.append(navigator)
34
13
  }
35
14
 
36
- /// Removes by identity rather than `removeLast` so a spurious disappear of a covered
37
- /// (non-top) container can't drop the wrong navigator.
38
15
  public func remove(_ navigator: Navigator) {
39
16
  navigators.removeAll { $0 === navigator }
40
17
  }
41
18
 
42
- /// True while any kit stack is mounted — tells the host to route the back into the kit
43
- /// instead of dismissing the mini-app. Matches `ComposeNavigatorManager.checkCanPop`:
44
- /// it does not inspect stack depth; `onBackSafe` handles the at-root case itself by
45
- /// asking the host to dismiss (via `composeApi`).
46
19
  public func checkCanPop() -> Bool { !navigators.isEmpty }
47
20
 
48
21
  public func pop() { navigators.last?.onBackSafe() }
@@ -18,6 +18,7 @@ public struct Information: View {
18
18
  private let action: String?
19
19
  private let showIconClose: Bool
20
20
  private let onPressClose: (() -> Void)?
21
+ private let highlightText: [String]
21
22
 
22
23
  public init(
23
24
  title: String? = nil,
@@ -28,7 +29,8 @@ public struct Information: View {
28
29
  showIcon: Bool = true,
29
30
  action: String? = nil,
30
31
  showIconClose: Bool = true,
31
- onPressClose: (() -> Void)? = nil
32
+ onPressClose: (() -> Void)? = nil,
33
+ highlightText: [String] = []
32
34
  ) {
33
35
  self.title = title
34
36
  self.description = description
@@ -39,6 +41,7 @@ public struct Information: View {
39
41
  self.action = action
40
42
  self.showIconClose = showIconClose
41
43
  self.onPressClose = onPressClose
44
+ self.highlightText = highlightText
42
45
  }
43
46
 
44
47
  private var borderColor: Color {
@@ -90,7 +93,7 @@ public struct Information: View {
90
93
  if let title {
91
94
  MomoText(title, typography: .labelDefaultMedium)
92
95
  }
93
- MomoText(description, typography: .descriptionDefaultRegular)
96
+ MomoText(description, typography: .descriptionDefaultRegular, highlightText: highlightText)
94
97
  }
95
98
  .frame(maxWidth: .infinity, alignment: .leading)
96
99
 
@@ -46,6 +46,42 @@ public func formatNumberToMoney(_ digits: String, currency: String = "đ") -> St
46
46
  return String(chars.reversed()) + currency
47
47
  }
48
48
 
49
+ private func isMoneyDigit(_ char: Character) -> Bool { char.isASCII && char.isNumber }
50
+
51
+ /// Applies an edit of `display[start..<end]` → `inserted` to the digits of `display`.
52
+ /// Separators and currency are never deleted: removing a separator deletes the
53
+ /// digit before it instead, removing only the currency leaves the digits untouched. Returns the new digits and the caret as a digit index.
54
+ private func applyMoneyEdit(_ display: String, start: Int, end: Int, inserted: String) -> (digits: String, caretDigit: Int) {
55
+ let chars = Array(display)
56
+ let start = min(start, chars.count)
57
+ let end = min(max(end, start), chars.count)
58
+ let digits = chars.filter(isMoneyDigit)
59
+ var left = chars[..<start].filter(isMoneyDigit).count
60
+ var removed = chars[start..<end].filter(isMoneyDigit).count
61
+ let insertedDigits = inserted.filter(isMoneyDigit)
62
+ if removed == 0 && insertedDigits.isEmpty && left > 0 && chars[start..<end].contains(".") {
63
+ left -= 1
64
+ removed = 1
65
+ }
66
+ if removed == 0 && insertedDigits.isEmpty {
67
+ return (String(digits), left)
68
+ }
69
+ let next = String(digits[..<left]) + insertedDigits + String(digits[(left + removed)...])
70
+ var trimmed = String(next.drop(while: { $0 == "0" }))
71
+ if trimmed.isEmpty { trimmed = String(next.prefix(1)) }
72
+ return (trimmed, max(0, left + insertedDigits.count - (next.count - trimmed.count)))
73
+ }
74
+
75
+ /// Caret offset right after the `digitIndex`-th digit of `display`.
76
+ private func moneyCaret(_ display: String, digitIndex: Int) -> Int {
77
+ var count = 0
78
+ for (index, char) in display.enumerated() where isMoneyDigit(char) {
79
+ count += 1
80
+ if count == digitIndex { return index + 1 }
81
+ }
82
+ return 0
83
+ }
84
+
49
85
  // MARK: - InputMoney
50
86
 
51
87
  public struct InputMoney: View {
@@ -261,8 +297,8 @@ public struct InputMoney: View {
261
297
  // MARK: - MoneyTextField (UIViewRepresentable)
262
298
 
263
299
  /// A UITextField that shows live thousands-separated currency while the bound
264
- /// `digits` string holds only the raw numeric value. Caret is pinned before
265
- /// the trailing currency symbol.
300
+ /// `digits` string holds only the raw numeric value. The caret can move freely
301
+ /// but never past the trailing currency symbol; edits apply at the caret.
266
302
  private struct MoneyTextField: UIViewRepresentable {
267
303
  @Binding var digits: String
268
304
  var keyboardType: UIKeyboardType
@@ -286,11 +322,6 @@ private struct MoneyTextField: UIViewRepresentable {
286
322
  field.setContentHuggingPriority(.defaultLow, for: .horizontal)
287
323
  field.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
288
324
  field.text = displayValue(digits)
289
- field.addTarget(
290
- context.coordinator,
291
- action: #selector(Coordinator.editingChanged(_:)),
292
- for: .editingChanged
293
- )
294
325
  return field
295
326
  }
296
327
 
@@ -302,12 +333,13 @@ private struct MoneyTextField: UIViewRepresentable {
302
333
  field.textColor = textColor
303
334
  field.tintColor = cursorColor
304
335
  let expected = displayValue(digits)
305
- if !field.isFirstResponder && field.text != expected {
336
+ if field.text != expected {
306
337
  field.text = expected
338
+ context.coordinator.setCaret(field, offset: moneyCaret(expected, digitIndex: expected.filter(isMoneyDigit).count))
307
339
  }
308
340
  }
309
341
 
310
- private func displayValue(_ raw: String) -> String {
342
+ fileprivate func displayValue(_ raw: String) -> String {
311
343
  let numeric = raw.filter { $0.isNumber }
312
344
  return numeric.isEmpty ? "" : formatNumberToMoney(numeric)
313
345
  }
@@ -330,21 +362,44 @@ private struct MoneyTextField: UIViewRepresentable {
330
362
  return true
331
363
  }
332
364
 
333
- @objc func editingChanged(_ textField: UITextField) {
334
- // Extract digits from whatever the user typed/deleted.
335
- let numeric = (textField.text ?? "").filter { $0.isNumber }
336
- let formatted = numeric.isEmpty ? "" : formatNumberToMoney(numeric)
337
- if textField.text != formatted {
338
- textField.text = formatted
339
- // Keep caret before the currency suffix ("đ").
340
- if let end = textField.position(from: textField.endOfDocument, offset: -1) {
341
- textField.selectedTextRange = textField.textRange(from: end, to: end)
342
- }
343
- }
365
+ func textField(
366
+ _ textField: UITextField,
367
+ shouldChangeCharactersIn range: NSRange,
368
+ replacementString string: String
369
+ ) -> Bool {
370
+ let (numeric, caretDigit) = applyMoneyEdit(
371
+ textField.text ?? "",
372
+ start: range.location,
373
+ end: range.location + range.length,
374
+ inserted: string
375
+ )
376
+ let formatted = parent.displayValue(numeric)
377
+ textField.text = formatted
378
+ setCaret(textField, offset: moneyCaret(formatted, digitIndex: caretDigit))
344
379
  if parent.digits != numeric {
345
380
  parent.digits = numeric
381
+ parent.onChangeText?(numeric)
382
+ }
383
+ return false
384
+ }
385
+
386
+ // Keeps the caret out of the currency suffix.
387
+ func textFieldDidChangeSelection(_ textField: UITextField) {
388
+ guard let text = textField.text, !text.isEmpty,
389
+ let range = textField.selectedTextRange,
390
+ let limit = textField.position(from: textField.endOfDocument, offset: -1)
391
+ else { return }
392
+ let start = textField.compare(range.start, to: limit) == .orderedDescending ? limit : range.start
393
+ let end = textField.compare(range.end, to: limit) == .orderedDescending ? limit : range.end
394
+ if start != range.start || end != range.end {
395
+ textField.selectedTextRange = textField.textRange(from: start, to: end)
396
+ }
397
+ }
398
+
399
+ func setCaret(_ textField: UITextField, offset: Int) {
400
+ if let position = textField.position(from: textField.beginningOfDocument, offset: offset) {
401
+ textField.selectedTextRange = textField.textRange(from: position, to: position)
346
402
  }
347
- parent.onChangeText?(numeric)
348
403
  }
349
404
  }
350
405
  }
@@ -139,6 +139,7 @@ public struct MomoText: View {
139
139
  private let letterSpacing: CGFloat?
140
140
  private let softWrap: Bool
141
141
  private let accessibilityId: String?
142
+ private var highlightText: [String] = []
142
143
 
143
144
  public init(
144
145
  _ content: String,
@@ -176,13 +177,52 @@ public struct MomoText: View {
176
177
  self.accessibilityId = accessibilityId
177
178
  }
178
179
 
180
+ init(_ content: String, typography: TypographyStyle, highlightText: [String]) {
181
+ self.init(content, typography: typography)
182
+ self.highlightText = highlightText
183
+ }
184
+
185
+ // Whole-word matches of `highlightText` render semibold; the longest keyword wins (RN parity).
186
+ private var baseText: SwiftUI.Text {
187
+ let keywords = highlightText.filter { !$0.isEmpty }.sorted { $0.count > $1.count }
188
+ guard !keywords.isEmpty else { return SwiftUI.Text(content) }
189
+ let highlightFont = momoFont(size: fontScale(typography.fontSize), weight: TypographyStyle.headerXsSemibold.fontWeight, family: theme.font)
190
+ func isWordChar(_ character: Character?) -> Bool {
191
+ guard let character else { return false }
192
+ return character.isLetter || character.isNumber || character == "_"
193
+ }
194
+
195
+ var result = SwiftUI.Text(verbatim: "")
196
+ var plainStart = content.startIndex
197
+ var index = content.startIndex
198
+ while index < content.endIndex {
199
+ let previous = index > content.startIndex ? content[content.index(before: index)] : nil
200
+ let keyword = isWordChar(previous) ? nil : keywords.first { keyword in
201
+ guard content[index...].hasPrefix(keyword) else { return false }
202
+ let end = content.index(index, offsetBy: keyword.count)
203
+ return !isWordChar(end < content.endIndex ? content[end] : nil)
204
+ }
205
+ guard let keyword else {
206
+ index = content.index(after: index)
207
+ continue
208
+ }
209
+ let end = content.index(index, offsetBy: keyword.count)
210
+ result = result
211
+ + SwiftUI.Text(verbatim: String(content[plainStart..<index]))
212
+ + SwiftUI.Text(verbatim: String(content[index..<end])).font(highlightFont)
213
+ plainStart = end
214
+ index = end
215
+ }
216
+ return result + SwiftUI.Text(verbatim: String(content[plainStart...]))
217
+ }
218
+
179
219
  public var body: some View {
180
220
  let textColor = color ?? theme.colors.text.default
181
221
  // Strikethrough is driven either by the typography variant (mirrors Compose
182
222
  // *Strikethrough styles) or the explicit `strikethrough` flag.
183
223
  let isStrikethrough = strikethrough || typography.isStrikethrough
184
224
 
185
- var text = SwiftUI.Text(content)
225
+ var text = baseText
186
226
  .font(momoFont(size: fontScale(typography.fontSize), weight: typography.fontWeight, family: theme.font))
187
227
  .foregroundColor(textColor)
188
228
  .strikethrough(isStrikethrough, color: textColor)
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKits'
3
- s.version = '0.165.1-beta.10'
3
+ s.version = '0.165.2-beta.2'
4
4
  s.summary = 'MoMoUIKits for iOS'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKitsDemo'
3
- s.version = '0.165.1-beta.10'
3
+ s.version = '0.165.2-beta.2'
4
4
  s.summary = 'Demo browser for MoMoUIKits SwiftUI components'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -44,6 +44,10 @@ private struct InformationPreview: View {
44
44
  Information(title: "Payment failed", description: "We couldn't process your payment.",
45
45
  state: .error, onPressAction: {}, action: "Retry")
46
46
  }
47
+ BasicColumnBox("Highlight text", alignment: .leading) {
48
+ Information(description: "Bạn được hoàn 50.000đ, áp dụng đến hết 31/12/2026",
49
+ highlightText: ["50.000đ", "31/12/2026"])
50
+ }
47
51
  BasicColumnBox("Without icon", alignment: .leading) {
48
52
  Information(title: "No icon", description: "showIcon is set to false.", showIcon: false)
49
53
  }
@@ -60,6 +64,7 @@ private struct InformationPlayground: View {
60
64
  @State private var showIcon = true
61
65
  @State private var showIconClose = false
62
66
  @State private var action = ""
67
+ @State private var highlightText = "description"
63
68
 
64
69
  var body: some View {
65
70
  PlaygroundTab(
@@ -72,12 +77,15 @@ private struct InformationPlayground: View {
72
77
  showIcon: showIcon,
73
78
  action: action.isEmpty ? nil : action,
74
79
  showIconClose: showIconClose,
75
- onPressClose: {}
80
+ onPressClose: {},
81
+ highlightText: highlightText.components(separatedBy: ",")
82
+ .map { $0.trimmingCharacters(in: .whitespaces) }
76
83
  )
77
84
  },
78
85
  params: [
79
86
  .string("Title", $title),
80
87
  .string("Description", $description),
88
+ .string("HighlightText: [String] (comma-separated)", $highlightText),
81
89
  .radio("State", informationStateList, $state),
82
90
  .checkbox("ShowIcon", $showIcon),
83
91
  .checkbox("ShowIconClose", $showIconClose),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.165.1-beta.10",
3
+ "version": "0.165.2-beta.2",
4
4
  "private": false,
5
5
  "dependeNavigationncies": {},
6
6
  "devDependencies": {},
package/publish.sh CHANGED
@@ -372,10 +372,34 @@ phase_publish_npm() {
372
372
  # FIRST PUBLISH: DEBUG VERSION (with compose kits included)
373
373
  # ============================================================
374
374
  echo ""
375
+ # A bad token surfaces as a 404 on the PUT, after the working tree was already rewritten
376
+ # below — so verify auth first, before anything is touched.
377
+ if ! npm whoami --registry https://registry.npmjs.org >/dev/null 2>&1; then
378
+ echo "❌ Error: npm auth failed (npm whoami). Renew the token in .npmrc / ~/.npmrc and retry."
379
+ exit 1
380
+ fi
381
+
375
382
  echo "🔧 Preparing debug version with compose kits..."
376
383
 
377
384
  # Create debug version
378
385
  DEBUG_VERSION="${VERSION}-debug"
386
+
387
+ # set -e aborts mid-phase on any failure; put the tree back no matter where it stopped.
388
+ # Idempotent: the happy path already moved the backups away and reset the version.
389
+ restore_npm_workspace() {
390
+ [ -f .npmignore.backup ] && mv .npmignore.backup .npmignore
391
+ [ -f compose/build.gradle.kts.backup ] && mv compose/build.gradle.kts.backup compose/build.gradle.kts
392
+ local pkg_version
393
+ pkg_version=$(node -p "require('./package.json').version")
394
+ if [ "$pkg_version" != "$VERSION" ]; then
395
+ if [[ "$OSTYPE" == "darwin"* ]]; then
396
+ sed -i '' "s/\"version\": \"$pkg_version\"/\"version\": \"$VERSION\"/" package.json
397
+ else
398
+ sed -i "s/\"version\": \"$pkg_version\"/\"version\": \"$VERSION\"/" package.json
399
+ fi
400
+ fi
401
+ }
402
+ trap restore_npm_workspace EXIT
379
403
  echo "📦 Debug version: $DEBUG_VERSION"
380
404
 
381
405
  # Get current version from package.json (in case it's different from $VERSION)
@@ -482,6 +506,7 @@ EOF
482
506
  npm publish --tag $NPM_TAG --access=public
483
507
 
484
508
  echo "✅ Normal version published successfully!"
509
+ trap - EXIT
485
510
 
486
511
  echo ""
487
512
  echo "✅ PHASE 3 COMPLETED"
Binary file
@@ -1,100 +0,0 @@
1
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 51873, v1.1.0). Idle timeout 300000ms.
2
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
3
- [CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
4
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 58673, v1.1.0). Idle timeout 300000ms.
5
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
6
- [CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
7
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 80799, v1.1.0). Idle timeout 300000ms.
8
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
9
- [CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
10
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 34253, v1.1.0). Idle timeout 300000ms.
11
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
12
- [CodeGraph daemon] Shutting down (idle timeout; clients=0).
13
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 65894, v1.1.0). Idle timeout 300000ms.
14
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
15
- [CodeGraph MCP] Auto-synced 1 file(s) in 446ms
16
- [CodeGraph MCP] Auto-synced 1 file(s) in 219ms
17
- [CodeGraph MCP] Auto-synced 1 file(s) in 212ms
18
- [CodeGraph daemon] Shutting down (idle timeout; clients=0).
19
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 68727, v1.1.0). Idle timeout 300000ms.
20
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
21
- [CodeGraph MCP] Auto-synced 1 file(s) in 466ms
22
- [CodeGraph MCP] Auto-synced 1 file(s) in 225ms
23
- [CodeGraph MCP] Auto-synced 1 file(s) in 218ms
24
- [CodeGraph MCP] Auto-synced 1 file(s) in 211ms
25
- [CodeGraph MCP] Auto-synced 1 file(s) in 203ms
26
- [CodeGraph MCP] Auto-synced 1 file(s) in 200ms
27
- [CodeGraph MCP] Auto-synced 1 file(s) in 204ms
28
- [CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
29
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 97060, v1.1.0). Idle timeout 300000ms.
30
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
31
- [CodeGraph MCP] Caught up 1 file(s) changed since last run
32
- [CodeGraph MCP] Auto-synced 1 file(s) in 485ms
33
- [CodeGraph MCP] Auto-synced 1 file(s) in 328ms
34
- [CodeGraph MCP] Auto-synced 1 file(s) in 280ms
35
- [CodeGraph MCP] Auto-synced 1 file(s) in 208ms
36
- [CodeGraph MCP] Auto-synced 1 file(s) in 206ms
37
- [CodeGraph MCP] Auto-synced 1 file(s) in 178ms
38
- [CodeGraph daemon] Shutting down (idle timeout; clients=0).
39
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 2726, v1.1.0). Idle timeout 300000ms.
40
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
41
- [CodeGraph MCP] Auto-synced 1 file(s) in 427ms
42
- [CodeGraph MCP] Auto-synced 1 file(s) in 178ms
43
- [CodeGraph MCP] Auto-synced 1 file(s) in 170ms
44
- [CodeGraph MCP] Auto-synced 1 file(s) in 196ms
45
- [CodeGraph MCP] Auto-synced 1 file(s) in 180ms
46
- [CodeGraph MCP] Auto-synced 1 file(s) in 146ms
47
- [CodeGraph MCP] Auto-synced 1 file(s) in 170ms
48
- [CodeGraph MCP] Auto-synced 1 file(s) in 190ms
49
- [CodeGraph MCP] Auto-synced 1 file(s) in 178ms
50
- [CodeGraph daemon] Shutting down (idle timeout; clients=0).
51
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 8036, v1.1.0). Idle timeout 300000ms.
52
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
53
- [CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
54
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 14133, v1.1.0). Idle timeout 300000ms.
55
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
56
- [CodeGraph daemon] Shutting down (idle timeout; clients=0).
57
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 34560, v1.1.0). Idle timeout 300000ms.
58
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
59
- [CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
60
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 73920, v1.1.0). Idle timeout 300000ms.
61
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
62
- [CodeGraph daemon] Shutting down (idle timeout; clients=0).
63
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 8855, v1.1.0). Idle timeout 300000ms.
64
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
65
- [CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
66
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 39954, v1.1.0). Idle timeout 300000ms.
67
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
68
- [CodeGraph MCP] Auto-synced 124 file(s) in 1892ms
69
- [CodeGraph MCP] Auto-synced 1 file(s) in 247ms
70
- [CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
71
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 70751, v1.1.0). Idle timeout 300000ms.
72
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
73
- [CodeGraph MCP] Caught up 1 file(s) changed since last run
74
- [CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
75
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 89552, v1.1.0). Idle timeout 300000ms.
76
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
77
- [CodeGraph daemon] Shutting down (idle timeout; clients=0).
78
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 98891, v1.1.0). Idle timeout 300000ms.
79
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
80
- [CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
81
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 57037, v1.1.0). Idle timeout 300000ms.
82
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
83
- [CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
84
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 32794, v1.1.0). Idle timeout 300000ms.
85
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
86
- [CodeGraph MCP] Caught up 4 file(s) changed since last run
87
- [CodeGraph daemon] Shutting down (idle timeout; clients=0).
88
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 57725, v1.1.0). Idle timeout 300000ms.
89
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
90
- [CodeGraph MCP] Caught up 17 file(s) changed since last run
91
- [CodeGraph MCP] Auto-synced 13 file(s) in 241ms
92
- [CodeGraph MCP] Auto-synced 13 file(s) in 246ms
93
- [CodeGraph MCP] Auto-synced 1 file(s) in 181ms
94
- [CodeGraph MCP] Auto-synced 1 file(s) in 186ms
95
- [CodeGraph MCP] Auto-synced 1 file(s) in 176ms
96
- [CodeGraph daemon] Shutting down (idle timeout; clients=0).
97
- [CodeGraph daemon] Listening on /Users/sonnguyen/Documents/Momo/momo-native-kits/.codegraph/daemon.sock (pid 26309, v1.1.0). Idle timeout 300000ms.
98
- [CodeGraph MCP] File watcher active — graph will auto-sync on changes
99
- [CodeGraph MCP] Caught up 267 file(s) changed since last run
100
- [CodeGraph daemon] Shutting down (inactivity backstop; clients=1).
package/build.gradle.kts DELETED
@@ -1,11 +0,0 @@
1
- plugins {
2
- // this is necessary to avoid the plugins to be loaded multiple times
3
- // in each subproject's classloader
4
- alias(libs.plugins.android.application) apply false
5
- alias(libs.plugins.android.library) apply false
6
- alias(libs.plugins.compose) apply false
7
- alias(libs.plugins.kotlin.compose.compiler) apply false
8
- alias(libs.plugins.jetbrains.kotlin.multiplatform) apply false
9
- alias(libs.plugins.jetbrains.kotlin.serialization) apply false
10
- alias(libs.plugins.vanniktech.mavenPublish) apply false
11
- }