@expo/ui 58.0.14 → 58.0.16

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 (130) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/ui/DatePickerView.kt +35 -2
  4. package/android/src/main/java/expo/modules/ui/DateRangePickerView.kt +35 -2
  5. package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +3 -0
  6. package/android/src/main/java/expo/modules/ui/HostView.kt +21 -0
  7. package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +2 -2
  8. package/android/src/main/java/expo/modules/ui/RNHostView.kt +117 -1
  9. package/android/src/main/java/expo/modules/ui/menu/DropdownMenu.kt +8 -1
  10. package/android/src/main/java/expo/modules/ui/menu/DropdownMenuRecords.kt +13 -1
  11. package/build/NativeTreeContext.d.ts +10 -0
  12. package/build/NativeTreeContext.js +25 -0
  13. package/build/NativeTreeContext.js.map +1 -0
  14. package/build/State/useWorkletCallback.d.ts +1 -1
  15. package/build/community/bottom-sheet/BottomSheet.ios.js +11 -4
  16. package/build/community/bottom-sheet/BottomSheet.ios.js.map +1 -1
  17. package/build/community/datetime-picker/DateTimePicker.android.js +4 -0
  18. package/build/community/datetime-picker/DateTimePicker.android.js.map +1 -1
  19. package/build/community/menu/MenuView.android.js +6 -0
  20. package/build/community/menu/MenuView.android.js.map +1 -1
  21. package/build/jetpack-compose/DatePicker/index.d.ts +38 -0
  22. package/build/jetpack-compose/DatePicker/index.js.map +1 -1
  23. package/build/jetpack-compose/DropdownMenu/index.d.ts +8 -1
  24. package/build/jetpack-compose/DropdownMenu/index.js.map +1 -1
  25. package/build/jetpack-compose/Host/index.js +8 -2
  26. package/build/jetpack-compose/Host/index.js.map +1 -1
  27. package/build/jetpack-compose/RNHostView/index.js +5 -1
  28. package/build/jetpack-compose/RNHostView/index.js.map +1 -1
  29. package/build/swift-ui/Chart/index.d.ts +10 -0
  30. package/build/swift-ui/Chart/index.js.map +1 -1
  31. package/build/swift-ui/Host/index.js +8 -2
  32. package/build/swift-ui/Host/index.js.map +1 -1
  33. package/build/swift-ui/RNHostView.js +5 -1
  34. package/build/swift-ui/RNHostView.js.map +1 -1
  35. package/build/swift-ui/Toolbar/index.d.ts +2 -0
  36. package/build/swift-ui/Toolbar/index.js +2 -0
  37. package/build/swift-ui/Toolbar/index.js.map +1 -1
  38. package/build/swift-ui/modifiers/hingeObservation.d.ts +1 -1
  39. package/build/swift-ui/modifiers/hingeObservation.js +1 -1
  40. package/build/swift-ui/modifiers/index.d.ts +1 -0
  41. package/build/swift-ui/modifiers/index.js +1 -0
  42. package/build/swift-ui/modifiers/index.js.map +1 -1
  43. package/build/swift-ui/modifiers/presentationModifiers.d.ts +17 -1
  44. package/build/swift-ui/modifiers/presentationModifiers.js +4 -2
  45. package/build/swift-ui/modifiers/presentationModifiers.js.map +1 -1
  46. package/build/swift-ui/modifiers/searchable.d.ts +83 -0
  47. package/build/swift-ui/modifiers/searchable.js +74 -0
  48. package/build/swift-ui/modifiers/searchable.js.map +1 -0
  49. package/build/types.d.ts +27 -0
  50. package/build/types.js +1 -1
  51. package/build/types.js.map +1 -1
  52. package/build/universal/BottomSheet/index.android.js +26 -18
  53. package/build/universal/BottomSheet/index.android.js.map +1 -1
  54. package/build/universal/BottomSheet/index.ios.js +20 -12
  55. package/build/universal/BottomSheet/index.ios.js.map +1 -1
  56. package/ios/ChartView.swift +15 -10
  57. package/ios/DataListForEachView.swift +1 -0
  58. package/ios/GridView.swift +1 -0
  59. package/ios/HostView.swift +27 -4
  60. package/ios/Modifiers/PresentationModifiers.swift +20 -4
  61. package/ios/Modifiers/SearchToolbarBehaviorModifier.swift +35 -0
  62. package/ios/Modifiers/SearchableModifier.swift +94 -0
  63. package/ios/Modifiers/ViewModifierRegistry.swift +8 -0
  64. package/ios/NamespaceView.swift +1 -0
  65. package/ios/Picker/PickerView.swift +9 -7
  66. package/ios/RNHostView.swift +19 -4
  67. package/ios/SlotView.swift +1 -0
  68. package/ios/TabView/TabView.swift +2 -0
  69. package/ios/ToolbarView.swift +2 -1
  70. package/ios/UIBaseView.swift +13 -0
  71. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{58.0.14/expo.modules.ui-58.0.14-sources.jar → 58.0.16/expo.modules.ui-58.0.16-sources.jar} +0 -0
  72. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16-sources.jar.md5 +1 -0
  73. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16-sources.jar.sha1 +1 -0
  74. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16-sources.jar.sha256 +1 -0
  75. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16-sources.jar.sha512 +1 -0
  76. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.aar +0 -0
  77. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.aar.md5 +1 -0
  78. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.aar.sha1 +1 -0
  79. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.aar.sha256 +1 -0
  80. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.aar.sha512 +1 -0
  81. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{58.0.14/expo.modules.ui-58.0.14.module → 58.0.16/expo.modules.ui-58.0.16.module} +22 -22
  82. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.module.md5 +1 -0
  83. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.module.sha1 +1 -0
  84. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.module.sha256 +1 -0
  85. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.module.sha512 +1 -0
  86. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{58.0.14/expo.modules.ui-58.0.14.pom → 58.0.16/expo.modules.ui-58.0.16.pom} +1 -1
  87. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.pom.md5 +1 -0
  88. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.pom.sha1 +1 -0
  89. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.pom.sha256 +1 -0
  90. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.16/expo.modules.ui-58.0.16.pom.sha512 +1 -0
  91. package/package.json +2 -2
  92. package/prebuilds/output/debug/xcframeworks/ExpoUI.tar.gz +0 -0
  93. package/prebuilds/output/release/xcframeworks/ExpoUI.tar.gz +0 -0
  94. package/src/NativeTreeContext.tsx +31 -0
  95. package/src/State/useWorkletCallback.ts +1 -1
  96. package/src/community/bottom-sheet/BottomSheet.ios.tsx +7 -6
  97. package/src/community/datetime-picker/DateTimePicker.android.tsx +4 -0
  98. package/src/community/menu/MenuView.android.tsx +12 -2
  99. package/src/jetpack-compose/DatePicker/index.tsx +38 -0
  100. package/src/jetpack-compose/DropdownMenu/index.tsx +12 -1
  101. package/src/jetpack-compose/Host/index.tsx +6 -2
  102. package/src/jetpack-compose/RNHostView/index.tsx +3 -1
  103. package/src/swift-ui/Chart/index.tsx +10 -0
  104. package/src/swift-ui/Host/index.tsx +6 -2
  105. package/src/swift-ui/RNHostView.tsx +3 -1
  106. package/src/swift-ui/Toolbar/index.tsx +2 -0
  107. package/src/swift-ui/modifiers/hingeObservation.ts +1 -1
  108. package/src/swift-ui/modifiers/index.ts +1 -0
  109. package/src/swift-ui/modifiers/presentationModifiers.ts +18 -2
  110. package/src/swift-ui/modifiers/searchable.ts +107 -0
  111. package/src/types.ts +28 -0
  112. package/src/universal/BottomSheet/index.android.tsx +23 -12
  113. package/src/universal/BottomSheet/index.ios.tsx +20 -9
  114. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14-sources.jar.md5 +0 -1
  115. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14-sources.jar.sha1 +0 -1
  116. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14-sources.jar.sha256 +0 -1
  117. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14-sources.jar.sha512 +0 -1
  118. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.aar +0 -0
  119. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.aar.md5 +0 -1
  120. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.aar.sha1 +0 -1
  121. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.aar.sha256 +0 -1
  122. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.aar.sha512 +0 -1
  123. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.module.md5 +0 -1
  124. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.module.sha1 +0 -1
  125. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.module.sha256 +0 -1
  126. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.module.sha512 +0 -1
  127. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.pom.md5 +0 -1
  128. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.pom.sha1 +0 -1
  129. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.pom.sha256 +0 -1
  130. package/local-maven-repo/expo/modules/ui/expo.modules.ui/58.0.14/expo.modules.ui-58.0.14.pom.sha512 +0 -1
@@ -0,0 +1,94 @@
1
+ // Copyright 2026-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoModulesCore
4
+ import SwiftUI
5
+
6
+ internal enum SearchFieldPlacementType: String, Enumerable {
7
+ case automatic
8
+ case toolbar
9
+ case sidebar
10
+ case navigationBarDrawer
11
+
12
+ var toSearchFieldPlacement: SearchFieldPlacement {
13
+ switch self {
14
+ case .automatic:
15
+ return .automatic
16
+ case .toolbar:
17
+ #if os(tvOS)
18
+ return .automatic
19
+ #else
20
+ return .toolbar
21
+ #endif
22
+ case .sidebar:
23
+ #if os(tvOS)
24
+ return .automatic
25
+ #else
26
+ return .sidebar
27
+ #endif
28
+ case .navigationBarDrawer:
29
+ #if os(iOS)
30
+ return .navigationBarDrawer
31
+ #else
32
+ return .automatic
33
+ #endif
34
+ }
35
+ }
36
+ }
37
+
38
+ internal struct SearchableModifier: ViewModifier, Record {
39
+ @Field var text: ObservableState?
40
+ @Field var placement: SearchFieldPlacementType = .automatic
41
+ @Field var prompt: String?
42
+ var eventDispatcher: EventDispatcher?
43
+
44
+ init() {}
45
+
46
+ init(from params: Dict, appContext: AppContext, eventDispatcher: EventDispatcher) throws {
47
+ try self = .init(from: params, appContext: appContext)
48
+ self.eventDispatcher = eventDispatcher
49
+ }
50
+
51
+ @ViewBuilder
52
+ func body(content: Content) -> some View {
53
+ if let text {
54
+ SearchableWrapper(
55
+ state: text,
56
+ placement: placement,
57
+ prompt: prompt,
58
+ eventDispatcher: eventDispatcher
59
+ ) {
60
+ content
61
+ }
62
+ } else {
63
+ content
64
+ }
65
+ }
66
+ }
67
+
68
+ private struct SearchableWrapper<C: View>: View {
69
+ @ObservedObject var state: ObservableState
70
+ let placement: SearchFieldPlacementType
71
+ let prompt: String?
72
+ let eventDispatcher: EventDispatcher?
73
+ @ViewBuilder let content: () -> C
74
+
75
+ var body: some View {
76
+ // The search field writes through the binding. Only report edits that come from the field,
77
+ // so a value written from JavaScript does not bounce back as an event.
78
+ let query = Binding<String>(
79
+ get: { state.value as? String ?? "" },
80
+ set: { newValue in
81
+ guard newValue != state.value as? String else {
82
+ return
83
+ }
84
+ state.value = newValue
85
+ eventDispatcher?(["searchable": ["text": newValue]])
86
+ }
87
+ )
88
+ if let prompt {
89
+ content().searchable(text: query, placement: placement.toSearchFieldPlacement, prompt: prompt)
90
+ } else {
91
+ content().searchable(text: query, placement: placement.toSearchFieldPlacement)
92
+ }
93
+ }
94
+ }
@@ -2380,6 +2380,14 @@ extension ViewModifierRegistry {
2380
2380
  return try ScrollPositionModifier(from: params, appContext: appContext, eventDispatcher: eventDispatcher)
2381
2381
  }
2382
2382
 
2383
+ register("searchable") { params, appContext, eventDispatcher in
2384
+ return try SearchableModifier(from: params, appContext: appContext, eventDispatcher: eventDispatcher)
2385
+ }
2386
+
2387
+ register("searchToolbarBehavior") { params, appContext, _ in
2388
+ return try SearchToolbarBehaviorModifier(from: params, appContext: appContext)
2389
+ }
2390
+
2383
2391
  register("pickerStyle") { params, appContext, _ in
2384
2392
  return try PickerStyleModifier(from: params, appContext: appContext)
2385
2393
  }
@@ -21,6 +21,7 @@ internal struct NamespaceView: ExpoSwiftUI.View {
21
21
  NamespaceRegistry.shared.registerNamespace(namespace, forKey: props.id)
22
22
 
23
23
  return Children()
24
+ .publishZeroContentOrigin(props.shadowNodeProxy)
24
25
  .onDisappear {
25
26
  NamespaceRegistry.shared.unregisterNamespace(forKey: props.id)
26
27
  }
@@ -12,6 +12,7 @@ internal final class PickerProps: UIBaseViewProps {
12
12
 
13
13
  internal struct PickerView: ExpoSwiftUI.View {
14
14
  @State var selection: AnyHashable?
15
+ @State private var previousSelection: AnyHashable?
15
16
  @ObservedObject var props: PickerProps
16
17
 
17
18
  init(props: PickerProps) {
@@ -38,11 +39,8 @@ internal struct PickerView: ExpoSwiftUI.View {
38
39
 
39
40
  picker
40
41
  .onChange(of: selection) { newValue in
41
- guard let newValue else { return }
42
- let currentSelection = Self.getHashableFromEither(props.selection)
43
- if currentSelection == newValue {
44
- return
45
- }
42
+ guard let newValue, newValue != previousSelection else { return }
43
+ previousSelection = newValue
46
44
  let payload: [String: Any]
47
45
  if let stringValue = newValue as? String {
48
46
  payload = ["selection": stringValue]
@@ -54,10 +52,14 @@ internal struct PickerView: ExpoSwiftUI.View {
54
52
  props.onSelectionChange(payload)
55
53
  }
56
54
  .onChange(of: props.selection) { newValue in
57
- selection = Self.getHashableFromEither(newValue)
55
+ let newSelection = Self.getHashableFromEither(newValue)
56
+ previousSelection = newSelection
57
+ selection = newSelection
58
58
  }
59
59
  .onAppear {
60
- selection = Self.getHashableFromEither(props.selection)
60
+ let initialSelection = Self.getHashableFromEither(props.selection)
61
+ previousSelection = initialSelection
62
+ selection = initialSelection
61
63
  }
62
64
  }
63
65
 
@@ -107,14 +107,17 @@ struct RNHostView: ExpoSwiftUI.View {
107
107
  private struct PublishContentOriginModifier: ViewModifier {
108
108
  let shadowNodeProxy: ExpoSwiftUI.ShadowNodeProxy
109
109
  let isEnabled: Bool
110
+ @Environment(\.expoHostingView) private var hostingView
110
111
 
111
112
  func body(content: Content) -> some View {
112
113
  if isEnabled {
113
114
  content
114
- .onGeometryChange(for: CGRect.self) { proxy in
115
- proxy.frame(in: .named(expoHostCoordinateSpace))
116
- } action: { frame in
117
- shadowNodeProxy.setContentOrigin?(frame.origin)
115
+ .onGeometryChange(for: CGPoint?.self) { proxy in
116
+ originInHost(proxy)
117
+ } action: { origin in
118
+ if let origin {
119
+ shadowNodeProxy.setContentOrigin?(origin)
120
+ }
118
121
  }
119
122
  .onDisappear {
120
123
  shadowNodeProxy.clearContentOrigin?()
@@ -123,6 +126,18 @@ private struct PublishContentOriginModifier: ViewModifier {
123
126
  content
124
127
  }
125
128
  }
129
+
130
+ // Relative to the `Host`'s UIKit view, which Yoga laid out, so a safe-area inset is included.
131
+ private func originInHost(_ proxy: GeometryProxy) -> CGPoint? {
132
+ #if os(macOS)
133
+ return proxy.frame(in: .named(expoHostCoordinateSpace)).origin
134
+ #else
135
+ guard let hostView = hostingView?.view, let window = hostView.window else {
136
+ return nil
137
+ }
138
+ return hostView.convert(proxy.frame(in: .global).origin, from: window)
139
+ #endif
140
+ }
126
141
  }
127
142
 
128
143
  // Tracks the `RCTSurfaceTouchHandler` attached to a hosted RN view so it can be detached when the
@@ -17,6 +17,7 @@ public struct SlotView: ExpoSwiftUI.View {
17
17
 
18
18
  public var body: some View {
19
19
  Children()
20
+ .publishZeroContentOrigin(props.shadowNodeProxy)
20
21
  }
21
22
 
22
23
  func extra<T>(_ key: String, as type: T.Type = T.self) -> T? {
@@ -56,7 +56,9 @@ internal struct TabView: ExpoSwiftUI.View {
56
56
  systemImage: tab.props.systemImage ?? "",
57
57
  value: tab.props.value
58
58
  ) {
59
+ // The tab's own body does not run here, so publish its zero content origin on its content.
59
60
  Self.tabContent(for: tab.props.children ?? [])
61
+ .publishZeroContentOrigin(tab.props.shadowNodeProxy)
60
62
  }
61
63
  #if !os(tvOS)
62
64
  .badge(Self.badgeText(from: tab.props.modifiers))
@@ -15,7 +15,8 @@ private struct ToolbarContentView: View {
15
15
  let children = props.children ?? []
16
16
 
17
17
  if children.contains(where: isItem) {
18
- children.reduce(base) { view, child in
18
+ // The content slot's own body does not run here, so publish its zero content origin on the base.
19
+ children.reduce(AnyView(base.publishZeroContentOrigin(props.shadowNodeProxy))) { view, child in
19
20
  let childView: any View = child.childView
20
21
 
21
22
  guard let slot = child.childView as? SlotView, slot.props.name == "item" else {
@@ -19,6 +19,19 @@ public struct UIBaseView<Props: UIBaseViewProps, Content: ExpoSwiftUI.View<Props
19
19
  innerView
20
20
  .applyAccessibilityIdentifier(props.testID)
21
21
  .applyModifiers(props.modifiers, appContext: props.appContext, globalEventDispatcher: props.globalEventDispatcher)
22
+ .publishZeroContentOrigin(props.shadowNodeProxy)
23
+ }
24
+ }
25
+
26
+ extension View {
27
+ /**
28
+ SwiftUI places this view, so `measure()` must skip its Yoga origin. A zero content origin does that,
29
+ and `RNHostView` reports its own position inside the `Host`. Core clears the entry on teardown.
30
+ */
31
+ func publishZeroContentOrigin(_ shadowNodeProxy: ExpoSwiftUI.ShadowNodeProxy) -> some View {
32
+ onAppear {
33
+ shadowNodeProxy.setContentOrigin?(.zero)
34
+ }
22
35
  }
23
36
  }
24
37
 
@@ -0,0 +1 @@
1
+ 101a40d45fc8e73887c9518fbc88c31e2a700ca6e62d2cc96dd65f72459a9d69
@@ -0,0 +1 @@
1
+ 79141ef63c1b975a30858d8c1ca7bccde8d14a64d337883063b3c1cbe44f977cc40e0fda89d3076fd84b5e47308e86b3659c71744a4fc3cac2b858a0686f38c6
@@ -0,0 +1 @@
1
+ 64369f3b1f16363a28412ac36309b53dc2a29752
@@ -0,0 +1 @@
1
+ b8e480f4d8449359c2f5686c3614c9aa82ef98af9b7ce0edd4d839d14de7696f
@@ -0,0 +1 @@
1
+ 9e42c8a718971e6d77015353c6747570385fe0c1d901cf388a50509e00c9ee8581a81ba88d0e83a611b7ec5a6d1dff4280ecf81ed92776d2cf7c26f48c0048c5
@@ -3,7 +3,7 @@
3
3
  "component": {
4
4
  "group": "expo.modules.ui",
5
5
  "module": "expo.modules.ui",
6
- "version": "58.0.14",
6
+ "version": "58.0.16",
7
7
  "attributes": {
8
8
  "org.gradle.status": "release"
9
9
  }
@@ -24,13 +24,13 @@
24
24
  },
25
25
  "files": [
26
26
  {
27
- "name": "expo.modules.ui-58.0.14.aar",
28
- "url": "expo.modules.ui-58.0.14.aar",
29
- "size": 1843673,
30
- "sha512": "ef66aaa30d2d2703c4ff5c36c117ffaf8dd41a2d2a9a8a286224b817f77d20ae2298cd6e0959e1a78ccc3c741cbc286db2e8d5a086b4ed9e11acc6a206207d68",
31
- "sha256": "dc0a8e5a28e1f4f7d722e8706b5858844aa6eac089e89e5f8099374b309bed60",
32
- "sha1": "1409db74c61bae87d4830fa0652a822c1ba21d80",
33
- "md5": "26e374a25a5054c68e67a99ea46b3858"
27
+ "name": "expo.modules.ui-58.0.16.aar",
28
+ "url": "expo.modules.ui-58.0.16.aar",
29
+ "size": 1858313,
30
+ "sha512": "9e42c8a718971e6d77015353c6747570385fe0c1d901cf388a50509e00c9ee8581a81ba88d0e83a611b7ec5a6d1dff4280ecf81ed92776d2cf7c26f48c0048c5",
31
+ "sha256": "b8e480f4d8449359c2f5686c3614c9aa82ef98af9b7ce0edd4d839d14de7696f",
32
+ "sha1": "64369f3b1f16363a28412ac36309b53dc2a29752",
33
+ "md5": "812db5879217f7bbe3b44b5ffcb0725a"
34
34
  }
35
35
  ]
36
36
  },
@@ -141,13 +141,13 @@
141
141
  ],
142
142
  "files": [
143
143
  {
144
- "name": "expo.modules.ui-58.0.14.aar",
145
- "url": "expo.modules.ui-58.0.14.aar",
146
- "size": 1843673,
147
- "sha512": "ef66aaa30d2d2703c4ff5c36c117ffaf8dd41a2d2a9a8a286224b817f77d20ae2298cd6e0959e1a78ccc3c741cbc286db2e8d5a086b4ed9e11acc6a206207d68",
148
- "sha256": "dc0a8e5a28e1f4f7d722e8706b5858844aa6eac089e89e5f8099374b309bed60",
149
- "sha1": "1409db74c61bae87d4830fa0652a822c1ba21d80",
150
- "md5": "26e374a25a5054c68e67a99ea46b3858"
144
+ "name": "expo.modules.ui-58.0.16.aar",
145
+ "url": "expo.modules.ui-58.0.16.aar",
146
+ "size": 1858313,
147
+ "sha512": "9e42c8a718971e6d77015353c6747570385fe0c1d901cf388a50509e00c9ee8581a81ba88d0e83a611b7ec5a6d1dff4280ecf81ed92776d2cf7c26f48c0048c5",
148
+ "sha256": "b8e480f4d8449359c2f5686c3614c9aa82ef98af9b7ce0edd4d839d14de7696f",
149
+ "sha1": "64369f3b1f16363a28412ac36309b53dc2a29752",
150
+ "md5": "812db5879217f7bbe3b44b5ffcb0725a"
151
151
  }
152
152
  ]
153
153
  },
@@ -161,13 +161,13 @@
161
161
  },
162
162
  "files": [
163
163
  {
164
- "name": "expo.modules.ui-58.0.14-sources.jar",
165
- "url": "expo.modules.ui-58.0.14-sources.jar",
166
- "size": 110723,
167
- "sha512": "3e6c99d1b6aa74d01d30339d222653a631dae2abf99075e4c25fc171fbb33f9d55bca7daee4d1cd4c6ecdcdc89fb71067de948ac4a91135b6ce38cbb337c52d1",
168
- "sha256": "eee15e7c9f3aeb7b3941ead7ce0e72d8174b0b95964c69ef5a1d5fbf02536fbe",
169
- "sha1": "e587fcde7f1c58c339f95759565034f989a49dcf",
170
- "md5": "041ed0d6d536bbcf8d08e7a653bea8ef"
164
+ "name": "expo.modules.ui-58.0.16-sources.jar",
165
+ "url": "expo.modules.ui-58.0.16-sources.jar",
166
+ "size": 112962,
167
+ "sha512": "79141ef63c1b975a30858d8c1ca7bccde8d14a64d337883063b3c1cbe44f977cc40e0fda89d3076fd84b5e47308e86b3659c71744a4fc3cac2b858a0686f38c6",
168
+ "sha256": "101a40d45fc8e73887c9518fbc88c31e2a700ca6e62d2cc96dd65f72459a9d69",
169
+ "sha1": "aef15fcbe60dff9cbb6491595df9269d819f7065",
170
+ "md5": "6b0b0d2a72178b6d25a038ee47271abb"
171
171
  }
172
172
  ]
173
173
  }
@@ -0,0 +1 @@
1
+ 81f23fddc089c0a466c08356e6c33881fd08d65c
@@ -0,0 +1 @@
1
+ d57d5ce3086debe5c6c7760679de69409aa264c6871d034bac2140af6245f9f4
@@ -0,0 +1 @@
1
+ 44da04bba32c6fbb281c187d7382eabf9cf3cf674dc9f469335345dfe7268e2818a725531749822cd788eb96ce8c56ecb3535f30c757da77fb67630c050de4f3
@@ -9,7 +9,7 @@
9
9
  <modelVersion>4.0.0</modelVersion>
10
10
  <groupId>expo.modules.ui</groupId>
11
11
  <artifactId>expo.modules.ui</artifactId>
12
- <version>58.0.14</version>
12
+ <version>58.0.16</version>
13
13
  <packaging>aar</packaging>
14
14
  <name>expo.modules.ui</name>
15
15
  <url>https://github.com/expo/expo</url>
@@ -0,0 +1 @@
1
+ de7f94f1b4c3356f8244f8b620146bb9f37c52b8
@@ -0,0 +1 @@
1
+ 2bc1691b86aa0689fbf3bd19f3904aed5b40d206a8a152a03bf27c92eb871f28
@@ -0,0 +1 @@
1
+ 528fce22a7b79c091d7dab483702c8e99ae783b76bd79c6b11e2086d9c59308f28a1b77e9379d8dc81f3ce6b3916f100d8c5c873ac165f75f780262ae8f56264
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/ui",
3
- "version": "58.0.14",
3
+ "version": "58.0.16",
4
4
  "description": "A collection of UI components",
5
5
  "keywords": [
6
6
  "Jetpack Compose",
@@ -141,6 +141,7 @@
141
141
  },
142
142
  "devDependencies": {
143
143
  "@babel/core": "^7.26.0",
144
+ "@expo/internal-scripts": "58.0.1",
144
145
  "@playwright/test": "^1.59.0",
145
146
  "@testing-library/react": "^16.3.0",
146
147
  "@testing-library/react-native": "^14.0.1",
@@ -148,7 +149,6 @@
148
149
  "@types/jest": "^30.0.0",
149
150
  "@types/node": "^22.14.0",
150
151
  "@types/react": "~19.3.0",
151
- "expo-module-scripts": "56.0.8",
152
152
  "react-native-reanimated": "4.7.0",
153
153
  "react-native-worklets": "0.13.0"
154
154
  },
@@ -0,0 +1,31 @@
1
+ import { createContext, useContext, useEffect } from 'react';
2
+
3
+ /**
4
+ * `true` inside a `Host`, where SwiftUI or Jetpack Compose renders the content. `RNHostView` sets it
5
+ * back to `false`, because its children are React Native views again.
6
+ */
7
+ export const NativeTreeContext = createContext(false);
8
+
9
+ export function useIsInsideNativeTree(): boolean {
10
+ return useContext(NativeTreeContext);
11
+ }
12
+
13
+ let warned = false;
14
+
15
+ /**
16
+ * Warns once in development when a `Host` renders directly inside another `Host`'s native content.
17
+ */
18
+ export function useWarnIfNestedHost(layoutSystem: 'SwiftUI' | 'Jetpack Compose'): void {
19
+ const isNested = useIsInsideNativeTree();
20
+ useEffect(() => {
21
+ if (__DEV__ && isNested && !warned) {
22
+ warned = true;
23
+ console.warn(
24
+ `[@expo/ui] A \`Host\` is rendered directly inside another \`Host\`'s ${layoutSystem} content. ` +
25
+ `${layoutSystem} places it, but React Native can't see where, so its layout and \`measure()\` ` +
26
+ `can be wrong, and a \`Pressable\` inside it can cancel its press. Wrap the nested \`Host\` in ` +
27
+ `\`RNHostView\`, or render its ${layoutSystem} components directly in the outer \`Host\`.`
28
+ );
29
+ }
30
+ }, [isNested, layoutSystem]);
31
+ }
@@ -4,7 +4,7 @@ import { worklets } from './optionalWorklets';
4
4
  import { useWorkletProp } from './useWorkletProp';
5
5
 
6
6
  /**
7
- * A worklet function wrapped for native code, returned by [`useWorkletCallback`](#useworkletcallback).
7
+ * A worklet function wrapped for native code, returned by [`useWorkletCallback`](#useworkletcallbackcallback).
8
8
  * Pass it to a modifier or prop that accepts worklet callbacks, which then runs it synchronously on
9
9
  * the UI thread.
10
10
  */
@@ -199,8 +199,9 @@ export function BottomSheet(props: BottomSheetProps) {
199
199
  const bg = extractBackgroundColor(backgroundStyle);
200
200
  return [
201
201
  ...(fitToContents
202
- ? // Makes the iPad sheet size to that content instead of opening near full height.
203
- [presentationSizing('fitted')]
202
+ ? // Fits the sheet height to the content (iPad opens near full height otherwise) and lets
203
+ // the system choose the width.
204
+ [presentationSizing('automatic', { fitted: { horizontal: false, vertical: true } })]
204
205
  : [
205
206
  presentationDetents(detents, {
206
207
  selection: selectedDetent,
@@ -231,16 +232,16 @@ export function BottomSheet(props: BottomSheetProps) {
231
232
  onDismiss={fireCloseCallbacks}
232
233
  fitToContents={fitToContents}>
233
234
  <Group modifiers={modifiers}>
234
- <RNHostView matchContents={fitToContents}>
235
+ <RNHostView matchContents={fitToContents ? { vertical: true } : false}>
235
236
  {/* paddingTop compensates for tighter spacing between native drag indicator and content
236
237
  compared to gorhom's handle. flexGrow:1 + height:0 (flex-basis 0) fills the snap-point
237
238
  height without inheriting the scrollable child's intrinsic content height. With matchContents,
238
- RNHostView lays the hosted view out at its own size, so `width` gives it the sheet width
239
- while its height stays content-sized. */}
239
+ RNHostView takes the width from the sheet, which can be narrower than the window, and
240
+ the height from the content. */}
240
241
  <View
241
242
  style={
242
243
  fitToContents
243
- ? { width, paddingTop: handleComponent !== null ? 16 : 0 }
244
+ ? { paddingTop: handleComponent !== null ? 16 : 0 }
244
245
  : { flexGrow: 1, height: 0, paddingTop: handleComponent !== null ? 16 : 0 }
245
246
  }>
246
247
  <SheetScrollContextReset>{children}</SheetScrollContextReset>
@@ -129,6 +129,10 @@ export function DateTimePicker(props: DateTimePickerProps) {
129
129
  color={accentColor}
130
130
  onDateSelected={onDateSelected}
131
131
  showVariantToggle={false}
132
+ // Match iOS, where the inline picker has no label: hide Material 3's "Select date" title
133
+ // and its selected-date headline.
134
+ showTitle={false}
135
+ showHeadline={false}
132
136
  {...(testID ? { testID } : undefined)}
133
137
  />
134
138
  </Host>
@@ -12,8 +12,12 @@ import { Icon } from '../../jetpack-compose/Icon';
12
12
  import { RNHostView } from '../../jetpack-compose/RNHostView';
13
13
  import { Text as ComposeText } from '../../jetpack-compose/Text';
14
14
  import { useMaterialColors } from '../../jetpack-compose/colors';
15
+ import type { PopupProperties } from '../../types';
15
16
  import type { MenuAction, MenuComponentProps, MenuComponentRef, NativeActionEvent } from './types';
16
17
 
18
+ // Clipped popups drop the status bar offset, so edge-to-edge menus near the bottom detach from the anchor.
19
+ const POPUP_PROPERTIES: PopupProperties = { clippingEnabled: false };
20
+
17
21
  function actionId(action: MenuAction): string {
18
22
  return action.id ?? action.title;
19
23
  }
@@ -82,7 +86,10 @@ function MenuActionItem({ action, onPressAction, dismissAll, destructiveColor }:
82
86
  );
83
87
  }
84
88
  return (
85
- <DropdownMenu expanded={submenuExpanded} onDismissRequest={() => setSubmenuExpanded(false)}>
89
+ <DropdownMenu
90
+ expanded={submenuExpanded}
91
+ onDismissRequest={() => setSubmenuExpanded(false)}
92
+ properties={POPUP_PROPERTIES}>
86
93
  <DropdownMenu.Trigger>
87
94
  <DropdownMenuItem
88
95
  enabled={!attributes?.disabled}
@@ -191,7 +198,10 @@ export function MenuView(props: MenuComponentProps & { ref?: React.Ref<MenuCompo
191
198
  return (
192
199
  <View style={style} testID={testID}>
193
200
  <Host colorScheme={colorScheme} matchContents>
194
- <DropdownMenu expanded={expanded} onDismissRequest={dismissAll}>
201
+ <DropdownMenu
202
+ expanded={expanded}
203
+ onDismissRequest={dismissAll}
204
+ properties={POPUP_PROPERTIES}>
195
205
  <DropdownMenu.Trigger>
196
206
  <RNHostView matchContents>
197
207
  <Pressable
@@ -131,6 +131,20 @@ export interface DateTimePickerProps {
131
131
  * @default true
132
132
  */
133
133
  showVariantToggle?: boolean;
134
+ /**
135
+ * Whether the picker shows its title, such as "Select date". When `false`, the picker passes
136
+ * `null` to Material 3's `title` slot. This prop has no effect when `displayedComponents` is
137
+ * `'hourAndMinute'`.
138
+ * @default true
139
+ */
140
+ showTitle?: boolean;
141
+ /**
142
+ * Whether the picker shows its headline, which displays the current selection. When `false`,
143
+ * the picker passes `null` to Material 3's `headline` slot. This prop has no effect when
144
+ * `displayedComponents` is `'hourAndMinute'`.
145
+ * @default true
146
+ */
147
+ showHeadline?: boolean;
134
148
  /**
135
149
  * The components that the picker should display.
136
150
  * On Android, you can have a picker that selects just the date or just the time.
@@ -252,6 +266,18 @@ export interface DateRangePickerProps {
252
266
  * @default true
253
267
  */
254
268
  showVariantToggle?: boolean;
269
+ /**
270
+ * Whether the picker shows its title, such as "Select date". When `false`, the picker passes
271
+ * `null` to Material 3's `title` slot.
272
+ * @default true
273
+ */
274
+ showTitle?: boolean;
275
+ /**
276
+ * Whether the picker shows its headline, which displays the current selection. When `false`,
277
+ * the picker passes `null` to Material 3's `headline` slot.
278
+ * @default true
279
+ */
280
+ showHeadline?: boolean;
255
281
  /**
256
282
  * The tint color to use on the picker elements.
257
283
  */
@@ -332,6 +358,18 @@ export interface DatePickerDialogProps {
332
358
  initialDate?: string | null;
333
359
  variant?: AndroidVariant;
334
360
  showVariantToggle?: boolean;
361
+ /**
362
+ * Whether the picker shows its title, such as "Select date". When `false`, the picker passes
363
+ * `null` to Material 3's `title` slot.
364
+ * @default true
365
+ */
366
+ showTitle?: boolean;
367
+ /**
368
+ * Whether the picker shows its headline, which displays the current selection. When `false`,
369
+ * the picker passes `null` to Material 3's `headline` slot.
370
+ * @default true
371
+ */
372
+ showHeadline?: boolean;
335
373
  confirmButtonLabel?: string;
336
374
  dismissButtonLabel?: string;
337
375
  color?: ColorValue;
@@ -3,7 +3,7 @@ import type { ReactNode } from 'react';
3
3
  import type { StyleProp, ViewStyle, ColorValue } from 'react-native';
4
4
 
5
5
  import { PresentedContent } from '../../PresentedContentContext';
6
- import type { ModifierConfig } from '../../types';
6
+ import type { ModifierConfig, PopupProperties } from '../../types';
7
7
  import { createViewModifierEventListener } from '../modifiers/utils';
8
8
 
9
9
  const MenuNativeView: React.ComponentType<NativeMenuProps> = requireNativeView(
@@ -59,6 +59,13 @@ export interface DropdownMenuProps {
59
59
  */
60
60
  shadowElevation?: number;
61
61
 
62
+ /**
63
+ * Properties for the popup window of the dropdown menu.
64
+ *
65
+ * @platform android
66
+ */
67
+ properties?: PopupProperties;
68
+
62
69
  /**
63
70
  * Optional styles to apply to the `DropdownMenu`.
64
71
  */
@@ -76,6 +83,7 @@ type NativeMenuProps = {
76
83
  color?: ColorValue;
77
84
  cornerRadius?: number;
78
85
  shadowElevation?: number;
86
+ properties?: PopupProperties;
79
87
  style?: StyleProp<ViewStyle>;
80
88
  modifiers?: ModifierConfig[];
81
89
  children?: ReactNode;
@@ -129,3 +137,6 @@ DropdownMenu.Items = Items;
129
137
 
130
138
  export { DropdownMenu };
131
139
  export { DropdownMenuItem } from './DropdownMenuItem';
140
+
141
+ // Re-exported so the docs generator includes PopupProperties on the DropdownMenu API page.
142
+ export type { PopupProperties };