@expo/ui 57.0.21 → 57.0.23

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 (128) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/ui/DatePickerView.kt +36 -2
  4. package/android/src/main/java/expo/modules/ui/DateRangePickerView.kt +35 -2
  5. package/android/src/main/java/expo/modules/ui/HostView.kt +28 -1
  6. package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +23 -2
  7. package/android/src/main/java/expo/modules/ui/RNHostView.kt +124 -17
  8. package/build/community/bottom-sheet/BottomSheet.ios.d.ts.map +1 -1
  9. package/build/community/datetime-picker/DateTimePicker.android.d.ts.map +1 -1
  10. package/build/community/datetime-picker/utils.d.ts +11 -0
  11. package/build/community/datetime-picker/utils.d.ts.map +1 -0
  12. package/build/community/picker/Picker.android.d.ts.map +1 -1
  13. package/build/jetpack-compose/DatePicker/index.d.ts +38 -0
  14. package/build/jetpack-compose/DatePicker/index.d.ts.map +1 -1
  15. package/build/jetpack-compose/RNHostView/index.d.ts +6 -1
  16. package/build/jetpack-compose/RNHostView/index.d.ts.map +1 -1
  17. package/build/jetpack-compose/modifiers/index.d.ts +18 -3
  18. package/build/jetpack-compose/modifiers/index.d.ts.map +1 -1
  19. package/build/swift-ui/ArrangementView/index.d.ts +49 -0
  20. package/build/swift-ui/ArrangementView/index.d.ts.map +1 -0
  21. package/build/swift-ui/Chart/index.d.ts +10 -0
  22. package/build/swift-ui/Chart/index.d.ts.map +1 -1
  23. package/build/swift-ui/RNHostView.d.ts +7 -2
  24. package/build/swift-ui/RNHostView.d.ts.map +1 -1
  25. package/build/swift-ui/index.d.ts +1 -0
  26. package/build/swift-ui/index.d.ts.map +1 -1
  27. package/build/swift-ui/modifiers/animation/index.d.ts +6 -1
  28. package/build/swift-ui/modifiers/animation/index.d.ts.map +1 -1
  29. package/build/swift-ui/modifiers/animation/types.d.ts +15 -1
  30. package/build/swift-ui/modifiers/animation/types.d.ts.map +1 -1
  31. package/build/swift-ui/modifiers/arrangementModifiers.d.ts +110 -0
  32. package/build/swift-ui/modifiers/arrangementModifiers.d.ts.map +1 -0
  33. package/build/swift-ui/modifiers/containerBackground.d.ts +2 -2
  34. package/build/swift-ui/modifiers/index.d.ts +41 -2
  35. package/build/swift-ui/modifiers/index.d.ts.map +1 -1
  36. package/build/swift-ui/modifiers/presentationModifiers.d.ts +17 -1
  37. package/build/swift-ui/modifiers/presentationModifiers.d.ts.map +1 -1
  38. package/build/swift-ui/modifiers/searchable.d.ts +84 -0
  39. package/build/swift-ui/modifiers/searchable.d.ts.map +1 -0
  40. package/build/universal/RNHostView/index.d.ts.map +1 -1
  41. package/build/universal/RNHostView/types.d.ts +9 -4
  42. package/build/universal/RNHostView/types.d.ts.map +1 -1
  43. package/build/universal/Switch/index.android.d.ts.map +1 -1
  44. package/build/utils/matchContents.d.ts +11 -0
  45. package/build/utils/matchContents.d.ts.map +1 -0
  46. package/expo-module.config.json +1 -1
  47. package/ios/ArrangementViewView.swift +40 -0
  48. package/ios/ChartView.swift +15 -10
  49. package/ios/ExpoUIModule.swift +1 -1
  50. package/ios/Modifiers/AnimationConfig.swift +20 -0
  51. package/ios/Modifiers/ArrangementModifiers.swift +157 -0
  52. package/ios/Modifiers/ContainerBackgroundModifier.swift +26 -13
  53. package/ios/Modifiers/PreferredColorSchemeModifier.swift +12 -0
  54. package/ios/Modifiers/PresentationModifiers.swift +20 -4
  55. package/ios/Modifiers/SearchToolbarBehaviorModifier.swift +35 -0
  56. package/ios/Modifiers/SearchableModifier.swift +94 -0
  57. package/ios/Modifiers/View+ModifierArray.swift +3 -3
  58. package/ios/Modifiers/ViewModifierRegistry.swift +124 -9
  59. package/ios/Modifiers/WidgetKitModifiers.swift +13 -0
  60. package/ios/Picker/PickerView.swift +9 -7
  61. package/ios/RNHostView.swift +55 -9
  62. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{57.0.21/expo.modules.ui-57.0.21-sources.jar → 57.0.23/expo.modules.ui-57.0.23-sources.jar} +0 -0
  63. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23-sources.jar.md5 +1 -0
  64. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23-sources.jar.sha1 +1 -0
  65. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23-sources.jar.sha256 +1 -0
  66. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23-sources.jar.sha512 +1 -0
  67. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.aar +0 -0
  68. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.aar.md5 +1 -0
  69. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.aar.sha1 +1 -0
  70. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.aar.sha256 +1 -0
  71. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.aar.sha512 +1 -0
  72. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{57.0.21/expo.modules.ui-57.0.21.module → 57.0.23/expo.modules.ui-57.0.23.module} +22 -22
  73. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.module.md5 +1 -0
  74. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.module.sha1 +1 -0
  75. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.module.sha256 +1 -0
  76. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.module.sha512 +1 -0
  77. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{57.0.21/expo.modules.ui-57.0.21.pom → 57.0.23/expo.modules.ui-57.0.23.pom} +1 -1
  78. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.pom.md5 +1 -0
  79. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.pom.sha1 +1 -0
  80. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.pom.sha256 +1 -0
  81. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.23/expo.modules.ui-57.0.23.pom.sha512 +1 -0
  82. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml +4 -4
  83. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.md5 +1 -1
  84. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha1 +1 -1
  85. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha256 +1 -1
  86. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha512 +1 -1
  87. package/package.json +4 -4
  88. package/src/community/bottom-sheet/BottomSheet.ios.tsx +7 -6
  89. package/src/community/datetime-picker/DateTimePicker.android.tsx +18 -4
  90. package/src/community/datetime-picker/utils.ts +21 -0
  91. package/src/community/picker/Picker.android.tsx +5 -0
  92. package/src/jetpack-compose/DatePicker/index.tsx +38 -0
  93. package/src/jetpack-compose/RNHostView/index.tsx +17 -6
  94. package/src/jetpack-compose/modifiers/index.ts +17 -3
  95. package/src/swift-ui/ArrangementView/index.tsx +70 -0
  96. package/src/swift-ui/Chart/index.tsx +10 -0
  97. package/src/swift-ui/RNHostView.tsx +11 -4
  98. package/src/swift-ui/index.tsx +1 -0
  99. package/src/swift-ui/modifiers/animation/index.ts +24 -11
  100. package/src/swift-ui/modifiers/animation/types.ts +18 -0
  101. package/src/swift-ui/modifiers/arrangementModifiers.ts +142 -0
  102. package/src/swift-ui/modifiers/containerBackground.ts +2 -2
  103. package/src/swift-ui/modifiers/index.ts +52 -1
  104. package/src/swift-ui/modifiers/presentationModifiers.ts +18 -2
  105. package/src/swift-ui/modifiers/searchable.ts +107 -0
  106. package/src/universal/RNHostView/index.tsx +9 -4
  107. package/src/universal/RNHostView/types.ts +6 -4
  108. package/src/universal/Switch/index.android.tsx +11 -2
  109. package/src/utils/matchContents.ts +14 -0
  110. package/ios/AccessoryWidgetBackgroundView.swift +0 -27
  111. package/ios/Modifiers/WidgetModifiers.swift +0 -70
  112. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21-sources.jar.md5 +0 -1
  113. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21-sources.jar.sha1 +0 -1
  114. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21-sources.jar.sha256 +0 -1
  115. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21-sources.jar.sha512 +0 -1
  116. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.aar +0 -0
  117. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.aar.md5 +0 -1
  118. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.aar.sha1 +0 -1
  119. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.aar.sha256 +0 -1
  120. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.aar.sha512 +0 -1
  121. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.module.md5 +0 -1
  122. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.module.sha1 +0 -1
  123. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.module.sha256 +0 -1
  124. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.module.sha512 +0 -1
  125. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.pom.md5 +0 -1
  126. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.pom.sha1 +0 -1
  127. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.pom.sha256 +0 -1
  128. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.21/expo.modules.ui-57.0.21.pom.sha512 +0 -1
@@ -2,20 +2,18 @@
2
2
 
3
3
  import ExpoModulesCore
4
4
  import SwiftUI
5
- #if !os(tvOS)
6
- import WidgetKit
7
- #endif
8
5
 
9
6
  internal enum ContainerBackgroundPlacementOptions: String, Enumerable {
10
7
  case widget
11
8
  case navigation
12
9
  case navigationSplitView
13
10
 
14
- #if !os(tvOS)
11
+ #if !os(tvOS) && !os(macOS)
12
+ // `widget` is declared in WidgetKit, so expo-widgets applies it through `ViewModifierRegistry.widgetKit`.
15
13
  @available(iOS 18.0, *)
16
- var toContainerBackgroundPlacement: ContainerBackgroundPlacement {
14
+ var toContainerBackgroundPlacement: ContainerBackgroundPlacement? {
17
15
  switch self {
18
- case .widget: return .widget
16
+ case .widget: return nil
19
17
  case .navigation: return .navigation
20
18
  case .navigationSplitView: return .navigationSplitView
21
19
  }
@@ -28,20 +26,35 @@ internal struct ContainerBackgroundModifier: ViewModifier, Record {
28
26
  @Field var container: ContainerBackgroundPlacementOptions?
29
27
 
30
28
  func body(content: Content) -> some View {
31
- #if !os(tvOS)
29
+ #if os(tvOS) || os(macOS)
30
+ content
31
+ #else
32
32
  if let shapeStyle = style?.toAnyShapeStyle(), let container {
33
- if #available(iOS 18.0, *) {
34
- content.containerBackground(shapeStyle, for: container.toContainerBackgroundPlacement)
35
- } else if #available(iOS 17.0, *) {
36
- content.containerBackground(shapeStyle, for: .widget)
33
+ if container == .widget, let widgetKit = ViewModifierRegistry.widgetKit {
34
+ widgetKit.containerBackground(AnyView(content), style: shapeStyle)
35
+ } else if #available(iOS 18.0, *), let placement = container.toContainerBackgroundPlacement {
36
+ content.containerBackground(shapeStyle, for: placement)
37
+ } else if let widgetKit = ViewModifierRegistry.widgetKit {
38
+ // On iOS 17, the other placements fall back to `widget`.
39
+ widgetKit.containerBackground(AnyView(content), style: shapeStyle)
37
40
  } else {
38
41
  content
39
42
  }
40
43
  } else {
41
44
  content
42
45
  }
43
- #else
44
- content
45
46
  #endif
46
47
  }
48
+
49
+ #if DEBUG
50
+ private static var didWarnAboutWidgetPlacement = false
51
+
52
+ func warnIfWidgetPlacementIsUnavailable() {
53
+ guard container == .widget, ViewModifierRegistry.widgetKit == nil, !Self.didWarnAboutWidgetPlacement else {
54
+ return
55
+ }
56
+ Self.didWarnAboutWidgetPlacement = true
57
+ log.warn("containerBackground with the 'widget' placement has no effect here, because it only applies inside widgets rendered by expo-widgets. Use the 'navigation' or 'navigationSplitView' placement in app screens.")
58
+ }
59
+ #endif
47
60
  }
@@ -0,0 +1,12 @@
1
+ // Copyright 2025-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoModulesCore
4
+ import SwiftUI
5
+
6
+ internal struct PreferredColorSchemeModifier: ViewModifier, Record {
7
+ @Field var colorScheme: ColorSchemeType?
8
+
9
+ func body(content: Content) -> some View {
10
+ content.preferredColorScheme(colorScheme?.toNativeColorScheme())
11
+ }
12
+ }
@@ -238,23 +238,39 @@ internal enum PresentationSizingOption: String, Enumerable {
238
238
  case page
239
239
  }
240
240
 
241
+ internal struct PresentationSizingFittedAxes: Record {
242
+ @Field var horizontal: Bool = false
243
+ @Field var vertical: Bool = false
244
+ }
245
+
241
246
  internal struct PresentationSizingModifier: ViewModifier, Record {
242
247
  @Field var sizing: PresentationSizingOption = .automatic
248
+ @Field var fitted: PresentationSizingFittedAxes?
243
249
 
244
250
  func body(content: Content) -> some View {
245
251
  if #available(iOS 18.0, tvOS 18.0, macOS 15.0, *) {
246
252
  switch sizing {
247
253
  case .automatic:
248
- content.presentationSizing(.automatic)
254
+ applySizing(content, .automatic)
249
255
  case .fitted:
250
- content.presentationSizing(.fitted)
256
+ applySizing(content, .fitted)
251
257
  case .form:
252
- content.presentationSizing(.form)
258
+ applySizing(content, .form)
253
259
  case .page:
254
- content.presentationSizing(.page)
260
+ applySizing(content, .page)
255
261
  }
256
262
  } else {
257
263
  content
258
264
  }
259
265
  }
266
+
267
+ @available(iOS 18.0, tvOS 18.0, macOS 15.0, *)
268
+ @ViewBuilder
269
+ private func applySizing(_ content: Content, _ sizing: some PresentationSizing) -> some View {
270
+ if let fitted {
271
+ content.presentationSizing(sizing.fitted(horizontal: fitted.horizontal, vertical: fitted.vertical))
272
+ } else {
273
+ content.presentationSizing(sizing)
274
+ }
275
+ }
260
276
  }
@@ -0,0 +1,35 @@
1
+ // Copyright 2026-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoModulesCore
4
+ import SwiftUI
5
+
6
+ internal enum SearchToolbarBehaviorType: String, Enumerable {
7
+ case automatic
8
+ case minimize
9
+ }
10
+
11
+ internal struct SearchToolbarBehaviorModifier: ViewModifier, Record {
12
+ @Field var behavior: SearchToolbarBehaviorType = .automatic
13
+
14
+ @ViewBuilder
15
+ func body(content: Content) -> some View {
16
+ if #available(iOS 26.0, macOS 26.0, tvOS 26.0, *) {
17
+ #if compiler(>=6.2) // Xcode 26
18
+ switch behavior {
19
+ case .automatic:
20
+ content.searchToolbarBehavior(.automatic)
21
+ case .minimize:
22
+ #if os(macOS) || os(tvOS)
23
+ content.searchToolbarBehavior(.automatic)
24
+ #else
25
+ content.searchToolbarBehavior(.minimize)
26
+ #endif
27
+ }
28
+ #else
29
+ content
30
+ #endif
31
+ } else {
32
+ content
33
+ }
34
+ }
35
+ }
@@ -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
+ }
@@ -67,10 +67,10 @@ internal extension Image {
67
67
  )
68
68
  }
69
69
 
70
- if #available(iOS 18.0, *),
70
+ if let widgetKit = ViewModifierRegistry.widgetKit,
71
71
  let modifierConfig = modifiers.first(where: { $0["$type"] as? String == "widgetAccentedRenderingMode" }),
72
- let modifier = try? WidgetAccentedRenderingModeModifier(from: modifierConfig, appContext: appContext) {
73
- modifier.apply(to: image)
72
+ let view = widgetKit.widgetAccentedRenderingMode(image, params: modifierConfig, appContext: appContext) {
73
+ view
74
74
  } else {
75
75
  image
76
76
  }
@@ -1529,6 +1529,8 @@ public class ViewModifierRegistry {
1529
1529
  public typealias ModifierFactory = ([String: Any], AppContext, EventDispatcher) throws -> any ViewModifier
1530
1530
  private(set) internal var modifierFactories: [String: ModifierFactory] = [:]
1531
1531
 
1532
+ public static var widgetKit: WidgetKitModifiers?
1533
+
1532
1534
  private init() {
1533
1535
  registerBuiltInModifiers()
1534
1536
  }
@@ -1813,6 +1815,83 @@ internal struct NavigationTitleModifier: ViewModifier, Record {
1813
1815
  }
1814
1816
  }
1815
1817
 
1818
+ internal enum NavigationBarTitleDisplayMode: String, Enumerable {
1819
+ case automatic
1820
+ case inline
1821
+ case large
1822
+
1823
+ #if os(iOS)
1824
+ var value: NavigationBarItem.TitleDisplayMode {
1825
+ switch self {
1826
+ case .automatic:
1827
+ return .automatic
1828
+ case .inline:
1829
+ return .inline
1830
+ case .large:
1831
+ return .large
1832
+ }
1833
+ }
1834
+ #endif
1835
+ }
1836
+
1837
+ internal struct NavigationBarTitleDisplayModeModifier: ViewModifier, Record {
1838
+ @Field var displayMode: NavigationBarTitleDisplayMode = .automatic
1839
+
1840
+ func body(content: Content) -> some View {
1841
+ #if os(iOS)
1842
+ content.navigationBarTitleDisplayMode(displayMode.value)
1843
+ #else
1844
+ content
1845
+ #endif
1846
+ }
1847
+ }
1848
+
1849
+ internal enum ToolbarTitleDisplayModeType: String, Enumerable {
1850
+ case automatic
1851
+ case inline
1852
+ case inlineLarge
1853
+ case large
1854
+
1855
+ @available(iOS 17.0, tvOS 17.0, macOS 14.0, *)
1856
+ var value: SwiftUI.ToolbarTitleDisplayMode? {
1857
+ switch self {
1858
+ case .automatic:
1859
+ return .automatic
1860
+ case .inline:
1861
+ return .inline
1862
+ case .inlineLarge:
1863
+ #if os(tvOS)
1864
+ return nil
1865
+ #else
1866
+ return .inlineLarge
1867
+ #endif
1868
+ case .large:
1869
+ #if os(iOS)
1870
+ return .large
1871
+ #else
1872
+ return nil
1873
+ #endif
1874
+ }
1875
+ }
1876
+ }
1877
+
1878
+ internal struct ToolbarTitleDisplayModeModifier: ViewModifier, Record {
1879
+ @Field var mode: ToolbarTitleDisplayModeType = .automatic
1880
+
1881
+ @ViewBuilder
1882
+ func body(content: Content) -> some View {
1883
+ if #available(iOS 17.0, tvOS 17.0, macOS 14.0, *) {
1884
+ if let value = mode.value {
1885
+ content.toolbarTitleDisplayMode(value)
1886
+ } else {
1887
+ content
1888
+ }
1889
+ } else {
1890
+ content
1891
+ }
1892
+ }
1893
+ }
1894
+
1816
1895
  // MARK: - Built-in Modifier Registration
1817
1896
 
1818
1897
  // swiftlint:disable:next no_grouping_extension
@@ -1989,6 +2068,14 @@ extension ViewModifierRegistry {
1989
2068
  return try NavigationTitleModifier(from: params, appContext: appContext)
1990
2069
  }
1991
2070
 
2071
+ register("navigationBarTitleDisplayMode") { params, appContext, _ in
2072
+ return try NavigationBarTitleDisplayModeModifier(from: params, appContext: appContext)
2073
+ }
2074
+
2075
+ register("toolbarTitleDisplayMode") { params, appContext, _ in
2076
+ return try ToolbarTitleDisplayModeModifier(from: params, appContext: appContext)
2077
+ }
2078
+
1992
2079
  register("navigationSplitViewStyle") { params, appContext, _ in
1993
2080
  return try NavigationSplitViewStyleModifier(from: params, appContext: appContext)
1994
2081
  }
@@ -1997,6 +2084,26 @@ extension ViewModifierRegistry {
1997
2084
  return try NavigationSplitViewColumnWidthModifier(from: params, appContext: appContext)
1998
2085
  }
1999
2086
 
2087
+ register("arrangementViewStyle") { params, appContext, _ in
2088
+ return try ArrangementViewStyleModifier(from: params, appContext: appContext)
2089
+ }
2090
+
2091
+ register("splitArrangementLayoutRatio") { params, appContext, _ in
2092
+ return try SplitArrangementLayoutRatioModifier(from: params, appContext: appContext)
2093
+ }
2094
+
2095
+ register("splitArrangementLayoutSize") { params, appContext, _ in
2096
+ return try SplitArrangementLayoutSizeModifier(from: params, appContext: appContext)
2097
+ }
2098
+
2099
+ register("splitArrangementFixedLayoutSize") { params, appContext, _ in
2100
+ return try SplitArrangementFixedLayoutSizeModifier(from: params, appContext: appContext)
2101
+ }
2102
+
2103
+ register("overlayArrangementEdge") { params, appContext, _ in
2104
+ return try OverlayArrangementEdgeModifier(from: params, appContext: appContext)
2105
+ }
2106
+
2000
2107
  register("accessibilityLabel") { params, appContext, _ in
2001
2108
  return try AccessibilityLabelModifier(from: params, appContext: appContext)
2002
2109
  }
@@ -2269,6 +2376,14 @@ extension ViewModifierRegistry {
2269
2376
  return try ScrollPositionModifier(from: params, appContext: appContext, eventDispatcher: eventDispatcher)
2270
2377
  }
2271
2378
 
2379
+ register("searchable") { params, appContext, eventDispatcher in
2380
+ return try SearchableModifier(from: params, appContext: appContext, eventDispatcher: eventDispatcher)
2381
+ }
2382
+
2383
+ register("searchToolbarBehavior") { params, appContext, _ in
2384
+ return try SearchToolbarBehaviorModifier(from: params, appContext: appContext)
2385
+ }
2386
+
2272
2387
  register("pickerStyle") { params, appContext, _ in
2273
2388
  return try PickerStyleModifier(from: params, appContext: appContext)
2274
2389
  }
@@ -2305,6 +2420,10 @@ extension ViewModifierRegistry {
2305
2420
  return try ScrollDisabledModifier(from: params, appContext: appContext)
2306
2421
  }
2307
2422
 
2423
+ register("preferredColorScheme") { params, appContext, _ in
2424
+ return try PreferredColorSchemeModifier(from: params, appContext: appContext)
2425
+ }
2426
+
2308
2427
  register("scrollClipDisabled") { params, appContext, _ in
2309
2428
  return try ScrollClipDisabledModifier(from: params, appContext: appContext)
2310
2429
  }
@@ -2389,14 +2508,6 @@ extension ViewModifierRegistry {
2389
2508
  return try ContentTransitionModifier(from: params, appContext: appContext)
2390
2509
  }
2391
2510
 
2392
- register("widgetURL") { params, appContext, _ in
2393
- return try WidgetURLModifier(from: params, appContext: appContext)
2394
- }
2395
-
2396
- register("activityBackgroundTint") { params, appContext, _ in
2397
- return try ActivityBackgroundTintModifier(from: params, appContext: appContext)
2398
- }
2399
-
2400
2511
  register("keyboardType") { params, appContext, _ in
2401
2512
  return try KeyboardTypeModifier(from: params, appContext: appContext)
2402
2513
  }
@@ -2410,7 +2521,11 @@ extension ViewModifierRegistry {
2410
2521
  }
2411
2522
 
2412
2523
  register("containerBackground") { params, appContext, _ in
2413
- return try ContainerBackgroundModifier(from: params, appContext: appContext)
2524
+ let modifier = try ContainerBackgroundModifier(from: params, appContext: appContext)
2525
+ #if DEBUG
2526
+ modifier.warnIfWidgetPlacementIsUnavailable()
2527
+ #endif
2528
+ return modifier
2414
2529
  }
2415
2530
 
2416
2531
  register("symbolEffect") { params, appContext, _ in
@@ -0,0 +1,13 @@
1
+ // Copyright 2026-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoModulesCore
4
+ import SwiftUI
5
+
6
+ /**
7
+ * The parts of SwiftUI that are declared in WidgetKit. expo-widgets sets
8
+ * `ViewModifierRegistry.widgetKit`, so ExpoUI does not link WidgetKit.
9
+ */
10
+ public protocol WidgetKitModifiers {
11
+ func containerBackground(_ view: AnyView, style: AnyShapeStyle) -> AnyView
12
+ func widgetAccentedRenderingMode(_ image: Image, params: [String: Any], appContext: AppContext) -> AnyView?
13
+ }
@@ -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
 
@@ -4,7 +4,8 @@ import SwiftUI
4
4
  import ExpoModulesCore
5
5
 
6
6
  internal final class RNHostViewProps: ExpoSwiftUI.ViewProps {
7
- @Field var matchContents: Bool = false
7
+ @Field var matchContentsHorizontal: Bool = false
8
+ @Field var matchContentsVertical: Bool = false
8
9
  /**
9
10
  Adds LeafNode and MeasurableYogaNode trait in Shadow node
10
11
  */
@@ -50,8 +51,13 @@ struct RNHostView: ExpoSwiftUI.View {
50
51
 
51
52
  @ViewBuilder
52
53
  private var hostedContent: some View {
53
- if props.matchContents, let childUIView = firstChildUIView {
54
- ApplySizeFromYogaNode(childUIView: childUIView) {
54
+ if matchesContents, let childUIView = firstChildUIView {
55
+ ApplySizeFromYogaNode(
56
+ childUIView: childUIView,
57
+ horizontal: props.matchContentsHorizontal,
58
+ vertical: props.matchContentsVertical,
59
+ shadowNodeProxy: props.shadowNodeProxy
60
+ ) {
55
61
  Children()
56
62
  }
57
63
  .onAppear {
@@ -62,7 +68,7 @@ struct RNHostView: ExpoSwiftUI.View {
62
68
  .onDisappear {
63
69
  touchHandler.detach()
64
70
  }
65
- } else if props.matchContents {
71
+ } else if matchesContents {
66
72
  // No hosted UIView (a pure SwiftUI child, e.g. Text/Image). Render it at its
67
73
  // natural size instead of falling into the fill branch below, which would
68
74
  // stretch a self-sizing SwiftUI view to fill its container.
@@ -87,6 +93,10 @@ struct RNHostView: ExpoSwiftUI.View {
87
93
  }
88
94
  }
89
95
 
96
+ private var matchesContents: Bool {
97
+ props.matchContentsHorizontal || props.matchContentsVertical
98
+ }
99
+
90
100
  private var firstChildUIView: UIView? {
91
101
  props.children?.first?.uiView
92
102
  }
@@ -143,20 +153,52 @@ private final class RNHostTouchHandler: ObservableObject {
143
153
  }
144
154
  }
145
155
 
146
- // Sets SwiftUI view size from Yoga node size
156
+ // Sets SwiftUI view size from Yoga node size on the matched axes
147
157
  // Listens to Yoga node size changes and updates the SwiftUI view size
158
+ // An axis that is not matched fills the parent and reports its size to the Yoga node instead
148
159
  private struct ApplySizeFromYogaNode<Content: SwiftUI.View>: SwiftUI.View {
149
160
  @StateObject private var observer: Observer
161
+ let horizontal: Bool
162
+ let vertical: Bool
163
+ let shadowNodeProxy: ExpoSwiftUI.ShadowNodeProxy
150
164
  let content: Content
151
165
 
152
- init(childUIView: UIView, @ViewBuilder content: () -> Content) {
166
+ init(
167
+ childUIView: UIView,
168
+ horizontal: Bool,
169
+ vertical: Bool,
170
+ shadowNodeProxy: ExpoSwiftUI.ShadowNodeProxy,
171
+ @ViewBuilder content: () -> Content
172
+ ) {
153
173
  _observer = StateObject(wrappedValue: Observer(view: childUIView))
174
+ self.horizontal = horizontal
175
+ self.vertical = vertical
176
+ self.shadowNodeProxy = shadowNodeProxy
154
177
  self.content = content()
155
178
  }
156
179
 
157
180
  var body: some SwiftUI.View {
158
- content
159
- .frame(width: observer.size.width, height: observer.size.height)
181
+ if horizontal && vertical {
182
+ content
183
+ .frame(width: observer.size.width, height: observer.size.height)
184
+ } else {
185
+ content
186
+ .frame(
187
+ width: horizontal ? observer.size.width : nil,
188
+ height: vertical ? observer.size.height : nil
189
+ )
190
+ .frame(
191
+ maxWidth: horizontal ? nil : .infinity,
192
+ maxHeight: vertical ? nil : .infinity
193
+ )
194
+ .modifier(
195
+ ReportSizeToYogaNodeModifier(
196
+ shadowNodeProxy: shadowNodeProxy,
197
+ reportWidth: !horizontal,
198
+ reportHeight: !vertical
199
+ )
200
+ )
201
+ }
160
202
  }
161
203
 
162
204
  @MainActor
@@ -183,9 +225,13 @@ private struct ApplySizeFromYogaNode<Content: SwiftUI.View>: SwiftUI.View {
183
225
  // Listens to SwiftUI view size changes and updates the Yoga node size
184
226
  private struct ReportSizeToYogaNodeModifier: ViewModifier {
185
227
  let shadowNodeProxy: ExpoSwiftUI.ShadowNodeProxy
228
+ var reportWidth = true
229
+ var reportHeight = true
186
230
 
187
231
  private func handleSizeChange(_ size: CGSize) {
188
- shadowNodeProxy.setViewSize?(size)
232
+ shadowNodeProxy.setViewSize?(
233
+ CGSize(width: reportWidth ? size.width : .nan, height: reportHeight ? size.height : .nan)
234
+ )
189
235
  }
190
236
 
191
237
  func body(content: Content) -> some View {
@@ -0,0 +1 @@
1
+ 5f4063182b296fa0028d945a2621f9d8a86eb5219b0ad88492a18dfe4d737978
@@ -0,0 +1 @@
1
+ 0a89146da34d837d01eb682540df3405b70f6b1d18c50bedc2ecc1d0dee3188df777de03a5b629100950a0f6d3c2d9f89f52416641cb22d8f3a715c8e841ff18
@@ -0,0 +1 @@
1
+ bc99884c9e5f484800ffb10156cc8b50fa4d31c8
@@ -0,0 +1 @@
1
+ 45594b39ac8f0c6eb37b663d40160b8e93b6d5f56b985a739c8a03c6780d910f
@@ -0,0 +1 @@
1
+ 7ba6b0ca0e41c0aeaea90cfc7d49664bb88531a251ba79b1124009eba0b724b48ba1c306744111a40871213cdf5767638b931803dc2229c4ba1850b6cc87ca15