@expo/ui 55.0.0 → 55.0.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 (168) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/CONTRIBUTING.md +30 -0
  3. package/android/build.gradle +2 -2
  4. package/android/src/main/java/expo/modules/ui/DatePickerView.kt +90 -7
  5. package/android/src/main/java/expo/modules/ui/HorizontalFloatingToolbarView.kt +21 -8
  6. package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +105 -39
  7. package/android/src/main/java/expo/modules/ui/RNHostView.kt +204 -50
  8. package/android/src/main/java/expo/modules/ui/SwitchView.kt +70 -4
  9. package/android/src/main/java/expo/modules/ui/button/IconButton.kt +7 -1
  10. package/android/src/main/java/expo/modules/ui/convertibles/AnimatableFloat.kt +26 -0
  11. package/android/src/main/java/expo/modules/ui/convertibles/AnimationSpecParams.kt +93 -0
  12. package/android/src/main/java/expo/modules/ui/convertibles/GraphicsLayerParams.kt +24 -0
  13. package/build/jetpack-compose/Box/index.d.ts +14 -0
  14. package/build/jetpack-compose/Box/index.d.ts.map +1 -0
  15. package/build/jetpack-compose/Column/index.d.ts +22 -0
  16. package/build/jetpack-compose/Column/index.d.ts.map +1 -0
  17. package/build/jetpack-compose/DatePicker/index.d.ts +99 -0
  18. package/build/jetpack-compose/DatePicker/index.d.ts.map +1 -1
  19. package/build/jetpack-compose/FlowRow/index.d.ts +14 -0
  20. package/build/jetpack-compose/FlowRow/index.d.ts.map +1 -0
  21. package/build/jetpack-compose/LazyColumn/index.d.ts +0 -3
  22. package/build/jetpack-compose/LazyColumn/index.d.ts.map +1 -1
  23. package/build/jetpack-compose/Row/index.d.ts +22 -0
  24. package/build/jetpack-compose/Row/index.d.ts.map +1 -0
  25. package/build/jetpack-compose/Spacer/index.d.ts +2 -2
  26. package/build/jetpack-compose/Switch/index.d.ts +18 -0
  27. package/build/jetpack-compose/Switch/index.d.ts.map +1 -1
  28. package/build/jetpack-compose/index.d.ts +4 -1
  29. package/build/jetpack-compose/index.d.ts.map +1 -1
  30. package/build/jetpack-compose/layout-types.d.ts +26 -0
  31. package/build/jetpack-compose/layout-types.d.ts.map +1 -0
  32. package/build/jetpack-compose/layout.d.ts +5 -36
  33. package/build/jetpack-compose/layout.d.ts.map +1 -1
  34. package/build/jetpack-compose/modifiers/animation.d.ts +44 -0
  35. package/build/jetpack-compose/modifiers/animation.d.ts.map +1 -0
  36. package/build/jetpack-compose/modifiers/index.d.ts +50 -3
  37. package/build/jetpack-compose/modifiers/index.d.ts.map +1 -1
  38. package/build/swift-ui/AccessoryWidgetBackground/index.d.ts +4 -0
  39. package/build/swift-ui/AccessoryWidgetBackground/index.d.ts.map +1 -0
  40. package/build/swift-ui/ConfirmationDialog/index.d.ts.map +1 -1
  41. package/build/swift-ui/ContextMenu/index.d.ts.map +1 -1
  42. package/build/swift-ui/ControlGroup/index.d.ts +29 -0
  43. package/build/swift-ui/ControlGroup/index.d.ts.map +1 -0
  44. package/build/swift-ui/Gauge/index.d.ts.map +1 -1
  45. package/build/swift-ui/Image/index.d.ts +7 -1
  46. package/build/swift-ui/Image/index.d.ts.map +1 -1
  47. package/build/swift-ui/Label/index.d.ts.map +1 -1
  48. package/build/swift-ui/LabeledContent/index.d.ts.map +1 -1
  49. package/build/swift-ui/Menu/index.d.ts.map +1 -1
  50. package/build/swift-ui/Picker/index.d.ts.map +1 -1
  51. package/build/swift-ui/Popover/index.d.ts.map +1 -1
  52. package/build/swift-ui/Section/index.d.ts.map +1 -1
  53. package/build/swift-ui/Slider/index.d.ts.map +1 -1
  54. package/build/swift-ui/SlotView.d.ts +5 -0
  55. package/build/swift-ui/SlotView.d.ts.map +1 -0
  56. package/build/swift-ui/index.d.ts +2 -0
  57. package/build/swift-ui/index.d.ts.map +1 -1
  58. package/build/swift-ui/modifiers/index.d.ts +38 -1
  59. package/build/swift-ui/modifiers/index.d.ts.map +1 -1
  60. package/expo-module.config.json +1 -1
  61. package/ios/AccessoryWidgetBackgroundView.swift +27 -0
  62. package/ios/ConfirmationDialog/ConfirmationDialog.swift +3 -9
  63. package/ios/ConfirmationDialog/ConfirmationDialogProps.swift +0 -5
  64. package/ios/ContextMenu/ContextMenu.swift +27 -22
  65. package/ios/ContextMenu/ContextMenuRecords.swift +0 -6
  66. package/ios/ControlGroupView.swift +33 -0
  67. package/ios/ExpoUIModule.swift +10 -32
  68. package/ios/GaugeView.swift +4 -26
  69. package/ios/HostView.swift +1 -2
  70. package/ios/ImageView.swift +22 -11
  71. package/ios/Label.swift +2 -17
  72. package/ios/LabeledContentView.swift +3 -27
  73. package/ios/Menu/MenuRecords.swift +0 -2
  74. package/ios/Menu/MenuView.swift +2 -5
  75. package/ios/Modifiers/ResizableModifier.swift +24 -0
  76. package/ios/Modifiers/Rotation3DEffectModifier.swift +20 -0
  77. package/ios/Modifiers/View+ModifierArray.swift +29 -0
  78. package/ios/Modifiers/ViewModifierRegistry.swift +39 -3
  79. package/ios/Modifiers/WidgetModifiers.swift +46 -0
  80. package/ios/Picker/PickerView.swift +2 -6
  81. package/ios/Popover/PopoverProps.swift +0 -4
  82. package/ios/Popover/PopoverView.swift +2 -6
  83. package/ios/RNHostView.swift +91 -10
  84. package/ios/SectionView.swift +6 -12
  85. package/ios/SecureFieldView.swift +0 -1
  86. package/ios/ShareLink/ShareLinkView.swift +1 -1
  87. package/ios/SliderView.swift +10 -27
  88. package/ios/SlotView.swift +38 -0
  89. package/ios/TextFieldView.swift +0 -1
  90. package/ios/UIBaseView.swift +34 -3
  91. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{55.0.0/expo.modules.ui-55.0.0-sources.jar → 55.0.2/expo.modules.ui-55.0.2-sources.jar} +0 -0
  92. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2-sources.jar.md5 +1 -0
  93. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2-sources.jar.sha1 +1 -0
  94. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2-sources.jar.sha256 +1 -0
  95. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2-sources.jar.sha512 +1 -0
  96. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.aar +0 -0
  97. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.aar.md5 +1 -0
  98. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.aar.sha1 +1 -0
  99. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.aar.sha256 +1 -0
  100. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.aar.sha512 +1 -0
  101. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{55.0.0/expo.modules.ui-55.0.0.module → 55.0.2/expo.modules.ui-55.0.2.module} +22 -22
  102. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.module.md5 +1 -0
  103. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.module.sha1 +1 -0
  104. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.module.sha256 +1 -0
  105. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.module.sha512 +1 -0
  106. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{55.0.0/expo.modules.ui-55.0.0.pom → 55.0.2/expo.modules.ui-55.0.2.pom} +1 -1
  107. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.pom.md5 +1 -0
  108. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.pom.sha1 +1 -0
  109. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.pom.sha256 +1 -0
  110. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.2/expo.modules.ui-55.0.2.pom.sha512 +1 -0
  111. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml +4 -4
  112. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.md5 +1 -1
  113. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha1 +1 -1
  114. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha256 +1 -1
  115. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha512 +1 -1
  116. package/package.json +2 -2
  117. package/src/jetpack-compose/Box/index.tsx +26 -0
  118. package/src/jetpack-compose/Column/index.tsx +39 -0
  119. package/src/jetpack-compose/DatePicker/index.tsx +106 -2
  120. package/src/jetpack-compose/FlowRow/index.tsx +29 -0
  121. package/src/jetpack-compose/LazyColumn/index.tsx +0 -3
  122. package/src/jetpack-compose/Row/index.tsx +36 -0
  123. package/src/jetpack-compose/Spacer/index.tsx +2 -2
  124. package/src/jetpack-compose/Switch/index.tsx +18 -0
  125. package/src/jetpack-compose/index.ts +4 -1
  126. package/src/jetpack-compose/layout-types.ts +48 -0
  127. package/src/jetpack-compose/layout.tsx +5 -106
  128. package/src/jetpack-compose/modifiers/animation.ts +37 -0
  129. package/src/jetpack-compose/modifiers/index.ts +60 -4
  130. package/src/swift-ui/AccessoryWidgetBackground/index.tsx +12 -0
  131. package/src/swift-ui/ConfirmationDialog/index.tsx +4 -12
  132. package/src/swift-ui/ContextMenu/index.tsx +6 -20
  133. package/src/swift-ui/ControlGroup/index.tsx +59 -0
  134. package/src/swift-ui/Gauge/index.tsx +5 -20
  135. package/src/swift-ui/Image/index.tsx +7 -1
  136. package/src/swift-ui/Label/index.tsx +2 -5
  137. package/src/swift-ui/LabeledContent/index.tsx +3 -12
  138. package/src/swift-ui/Menu/index.tsx +2 -6
  139. package/src/swift-ui/Picker/index.tsx +4 -11
  140. package/src/swift-ui/Popover/index.tsx +3 -12
  141. package/src/swift-ui/Section/index.tsx +4 -9
  142. package/src/swift-ui/Slider/index.tsx +4 -12
  143. package/src/swift-ui/SlotView.tsx +8 -0
  144. package/src/swift-ui/index.tsx +2 -0
  145. package/src/swift-ui/modifiers/index.ts +54 -1
  146. package/ios/ConfirmationDialog/ConfirmationDialogComponents.swift +0 -26
  147. package/ios/ContextMenu/ContextMenuComponents.swift +0 -37
  148. package/ios/Menu/MenuComponents.swift +0 -12
  149. package/ios/Picker/PickerComponents.swift +0 -24
  150. package/ios/Popover/PopoverComponents.swift +0 -18
  151. package/ios/SectionComponents.swift +0 -34
  152. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0-sources.jar.md5 +0 -1
  153. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0-sources.jar.sha1 +0 -1
  154. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0-sources.jar.sha256 +0 -1
  155. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0-sources.jar.sha512 +0 -1
  156. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.aar +0 -0
  157. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.aar.md5 +0 -1
  158. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.aar.sha1 +0 -1
  159. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.aar.sha256 +0 -1
  160. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.aar.sha512 +0 -1
  161. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.module.md5 +0 -1
  162. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.module.sha1 +0 -1
  163. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.module.sha256 +0 -1
  164. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.module.sha512 +0 -1
  165. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.pom.md5 +0 -1
  166. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.pom.sha1 +0 -1
  167. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.pom.sha256 +0 -1
  168. package/local-maven-repo/expo/modules/ui/expo.modules.ui/55.0.0/expo.modules.ui-55.0.0.pom.sha512 +0 -1
@@ -7,24 +7,6 @@ final class LabeledContentProps: UIBaseViewProps {
7
7
  @Field var label: String?
8
8
  }
9
9
 
10
- internal final class LabeledContentLabelProps: ExpoSwiftUI.ViewProps {}
11
- internal struct LabeledContentLabel: ExpoSwiftUI.View {
12
- @ObservedObject var props: LabeledContentLabelProps
13
-
14
- var body: some View {
15
- Children()
16
- }
17
- }
18
-
19
- internal final class LabeledContentContentProps: ExpoSwiftUI.ViewProps {}
20
- internal struct LabeledContentContent: ExpoSwiftUI.View {
21
- @ObservedObject var props: LabeledContentContentProps
22
-
23
- var body: some View {
24
- Children()
25
- }
26
- }
27
-
28
10
  internal struct LabeledContentView: ExpoSwiftUI.View {
29
11
  @ObservedObject var props: LabeledContentProps
30
12
 
@@ -45,25 +27,19 @@ internal struct LabeledContentView: ExpoSwiftUI.View {
45
27
  }
46
28
 
47
29
  private var hasCustomLabel: Bool {
48
- props.children?
49
- .compactMap({ $0.childView as? LabeledContentLabel })
50
- .first != nil
30
+ props.children?.slot("label") != nil
51
31
  }
52
32
 
53
33
  @ViewBuilder
54
34
  private var contentChildren: some View {
55
- if let content = props.children?
56
- .compactMap({ $0.childView as? LabeledContentContent })
57
- .first {
35
+ if let content = props.children?.slot("content") {
58
36
  content
59
37
  }
60
38
  }
61
39
 
62
40
  @ViewBuilder
63
41
  private var customLabelContent: some View {
64
- if let labelContent = props.children?
65
- .compactMap({ $0.childView as? LabeledContentLabel })
66
- .first {
42
+ if let labelContent = props.children?.slot("label") {
67
43
  labelContent
68
44
  }
69
45
  }
@@ -8,5 +8,3 @@ internal final class MenuProps: UIBaseViewProps {
8
8
  @Field var hasPrimaryAction: Bool = false
9
9
  var onPrimaryAction = EventDispatcher()
10
10
  }
11
-
12
- internal final class MenuLabelProps: ExpoSwiftUI.ViewProps {}
@@ -9,8 +9,7 @@ internal struct MenuView: ExpoSwiftUI.View {
9
9
  // If label is a component, it is passed as a child, so we need to exclude it in order to display the menu content
10
10
  @ViewBuilder
11
11
  func ChildrenWithoutLabel() -> some View {
12
- let labelView = props.children?.first(where: { $0.childView is MenuLabel })
13
- ForEach(props.children?.filter { $0.id != labelView?.id } ?? [], id: \.id) { child in
12
+ ForEach(props.children?.withoutSlot("label") ?? [], id: \.id) { child in
14
13
  let view: any View = child.childView
15
14
  AnyView(view)
16
15
  }
@@ -18,9 +17,7 @@ internal struct MenuView: ExpoSwiftUI.View {
18
17
 
19
18
  var body: some View {
20
19
  if #available(iOS 14.0, tvOS 17.0, *) {
21
- let labelContent = props.children?
22
- .compactMap { $0.childView as? MenuLabel }
23
- .first
20
+ let labelContent = props.children?.slot("label")
24
21
 
25
22
  if props.hasPrimaryAction {
26
23
  // With primaryAction, tap triggers callback and long-press shows menu
@@ -0,0 +1,24 @@
1
+ // Copyright 2025-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoModulesCore
4
+ import SwiftUI
5
+
6
+ internal enum ResizingModeOptions: String, Enumerable {
7
+ case stretch
8
+ case tile
9
+
10
+ var toResizingMode: Image.ResizingMode {
11
+ switch self {
12
+ case .stretch: return .stretch
13
+ case .tile: return .tile
14
+ }
15
+ }
16
+ }
17
+
18
+ internal struct ResizableModifier: Record {
19
+ @Field var top: CGFloat = 0
20
+ @Field var leading: CGFloat = 0
21
+ @Field var bottom: CGFloat = 0
22
+ @Field var trailing: CGFloat = 0
23
+ @Field var resizingMode: ResizingModeOptions = .stretch
24
+ }
@@ -0,0 +1,20 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoModulesCore
4
+ import SwiftUI
5
+
6
+ internal struct Rotation3DEffectModifier: ViewModifier, Record {
7
+ @Field var angle: Double = 0
8
+ @Field var axisX: CGFloat = 0
9
+ @Field var axisY: CGFloat = 0
10
+ @Field var axisZ: CGFloat = 0
11
+ @Field var perspective: CGFloat = 1
12
+
13
+ func body(content: Content) -> some View {
14
+ content.rotation3DEffect(
15
+ .degrees(angle),
16
+ axis: (x: axisX, y: axisY, z: axisZ),
17
+ perspective: perspective
18
+ )
19
+ }
20
+ }
@@ -50,3 +50,32 @@ internal extension Text {
50
50
  }
51
51
  }
52
52
  }
53
+
54
+ internal extension Image {
55
+ @ViewBuilder
56
+ func applyImageModifiers(_ modifiers: ModifierArray?, appContext: AppContext?) -> some View {
57
+ if let modifiers, let appContext {
58
+ let image = modifiers.reduce(self) { currentImage, modifierConfig in
59
+ guard let type = modifierConfig["$type"] as? String else {
60
+ return currentImage
61
+ }
62
+ return ViewModifierRegistry.shared.applyImageModifier(
63
+ type,
64
+ to: currentImage,
65
+ appContext: appContext,
66
+ params: modifierConfig
67
+ )
68
+ }
69
+
70
+ if #available(iOS 18.0, *),
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)
74
+ } else {
75
+ image
76
+ }
77
+ } else {
78
+ self
79
+ }
80
+ }
81
+ }
@@ -158,6 +158,12 @@ internal struct ForegroundColorModifier: ViewModifier, Record {
158
158
  }
159
159
  }
160
160
 
161
+ internal struct LuminanceToAlphaModifier: ViewModifier, Record {
162
+ func body(content: Content) -> some View {
163
+ content.luminanceToAlpha()
164
+ }
165
+ }
166
+
161
167
  internal struct BoldModifier: ViewModifier, Record {
162
168
  func body(content: Content) -> some View {
163
169
  if #available(iOS 16.0, tvOS 16.0, *) {
@@ -774,7 +780,6 @@ internal struct ListRowBackground: ViewModifier, Record {
774
780
  }
775
781
  }
776
782
 
777
-
778
783
  internal enum VerticalEdgeOptions: String, Enumerable {
779
784
  case all
780
785
  case top
@@ -1100,7 +1105,7 @@ internal enum AxisOptions: String, Enumerable {
1100
1105
  case horizontal
1101
1106
  case vertical
1102
1107
  case both
1103
-
1108
+
1104
1109
  func toAxis() -> Axis.Set {
1105
1110
  switch self {
1106
1111
  case .vertical:
@@ -1301,7 +1306,7 @@ public class ViewModifierRegistry {
1301
1306
  }
1302
1307
 
1303
1308
  /**
1304
- * Applies `Text returning modifiers. Useful for Text concatenation in TextView.
1309
+ * Applies Text returning modifiers. Useful for Text concatenation in TextView.
1305
1310
  */
1306
1311
  func applyTextModifier(
1307
1312
  _ type: String,
@@ -1347,6 +1352,29 @@ public class ViewModifierRegistry {
1347
1352
  }
1348
1353
  }
1349
1354
 
1355
+ /**
1356
+ * Applies Image returning modifiers.
1357
+ */
1358
+ func applyImageModifier(
1359
+ _ type: String,
1360
+ to image: Image,
1361
+ appContext: AppContext,
1362
+ params: [String: Any]
1363
+ ) -> Image {
1364
+ switch type {
1365
+ case "resizable":
1366
+ guard let modifier = try? ResizableModifier(from: params, appContext: appContext)
1367
+ else { return image.resizable() }
1368
+ return image.resizable(capInsets: EdgeInsets(top: modifier.top, leading: modifier.leading, bottom: modifier.bottom, trailing: modifier.trailing), resizingMode: modifier.resizingMode.toResizingMode)
1369
+ default:
1370
+ #if DEBUG
1371
+ return image
1372
+ #else
1373
+ return image
1374
+ #endif
1375
+ }
1376
+ }
1377
+
1350
1378
  /**
1351
1379
  * Checks if a modifier type is registered.
1352
1380
  */
@@ -1494,6 +1522,10 @@ extension ViewModifierRegistry {
1494
1522
  return try RotationEffectModifier(from: params, appContext: appContext)
1495
1523
  }
1496
1524
 
1525
+ register("rotation3DEffect") { params, appContext, _ in
1526
+ return try Rotation3DEffectModifier(from: params, appContext: appContext)
1527
+ }
1528
+
1497
1529
  register("offset") { params, appContext, _ in
1498
1530
  return try OffsetModifier(from: params, appContext: appContext)
1499
1531
  }
@@ -1506,6 +1538,10 @@ extension ViewModifierRegistry {
1506
1538
  return try ForegroundStyleModifier(from: params, appContext: appContext)
1507
1539
  }
1508
1540
 
1541
+ register("luminanceToAlpha") { params, appContext, _ in
1542
+ return try LuminanceToAlphaModifier(from: params, appContext: appContext)
1543
+ }
1544
+
1509
1545
  register("bold") { params, appContext, _ in
1510
1546
  return try BoldModifier(from: params, appContext: appContext)
1511
1547
  }
@@ -0,0 +1,46 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoModulesCore
4
+ import SwiftUI
5
+ #if !os(tvOS)
6
+ import WidgetKit
7
+ #endif
8
+
9
+ internal enum WidgetAccentedRenderingModeOptions: String, Enumerable {
10
+ case accented
11
+ case desaturated
12
+ case accentedDesaturated
13
+ case fullColor
14
+
15
+ #if !os(tvOS)
16
+ @available(iOS 18.0, *)
17
+ var toWidgetAccentedRenderingMode: WidgetAccentedRenderingMode {
18
+ switch self {
19
+ case .accented: return .accented
20
+ case .accentedDesaturated: return .accentedDesaturated
21
+ case .desaturated: return .desaturated
22
+ case .fullColor: return .fullColor
23
+ }
24
+ }
25
+ #endif
26
+ }
27
+
28
+ /**
29
+ * This is a unique modifier that exists only on Image, but returns some View, and for this reason it cannot be a ViewModifier.
30
+ */
31
+ internal struct WidgetAccentedRenderingModeModifier: Record {
32
+ @Field var renderingMode: WidgetAccentedRenderingModeOptions?
33
+
34
+ @ViewBuilder
35
+ func apply(to image: Image) -> some View {
36
+ #if !os(tvOS)
37
+ if #available(iOS 18.0, *), renderingMode != nil {
38
+ image.widgetAccentedRenderingMode(renderingMode?.toWidgetAccentedRenderingMode)
39
+ } else {
40
+ image
41
+ }
42
+ #else
43
+ image
44
+ #endif
45
+ }
46
+ }
@@ -24,13 +24,9 @@ internal struct PickerView: ExpoSwiftUI.View {
24
24
 
25
25
  @ViewBuilder
26
26
  private func makePicker() -> some View {
27
- let content = (props.children?
28
- .compactMap { $0.childView as? PickerContentView }
29
- .first)
27
+ let content = props.children?.slot("content")
30
28
 
31
- let labelContent = props.children?
32
- .compactMap { $0.childView as? PickerLabelView }
33
- .first
29
+ let labelContent = props.children?.slot("label")
34
30
 
35
31
  if let systemImage = props.systemImage, let label = props.label {
36
32
  Picker(label, systemImage: systemImage, selection: $selection) { content }
@@ -8,10 +8,6 @@ internal class PopoverViewProps: UIBaseViewProps {
8
8
  @Field var arrowEdge: PopoverArrowEdgeOption?
9
9
  }
10
10
 
11
- internal final class PopoverViewContentPorps: ExpoSwiftUI.ViewProps {}
12
-
13
- internal final class PopoverViewPopContentPorps: ExpoSwiftUI.ViewProps {}
14
-
15
11
  internal enum PopoverAttachmentAnchorOption: String, Enumerable {
16
12
  case top
17
13
  case center
@@ -45,18 +45,14 @@ internal struct PopoverView: ExpoSwiftUI.View {
45
45
 
46
46
  @ViewBuilder
47
47
  private var triggerContent: some View {
48
- if let content = props.children?
49
- .compactMap({ $0.childView as? PopoverViewContent })
50
- .first {
48
+ if let content = props.children?.slot("trigger") {
51
49
  content
52
50
  }
53
51
  }
54
52
 
55
53
  @ViewBuilder
56
54
  private var popoverContent: some View {
57
- if let content = props.children?
58
- .compactMap({ $0.childView as? PopoverViewPopContent })
59
- .first {
55
+ if let content = props.children?.slot("popover") {
60
56
  content
61
57
  }
62
58
  }
@@ -1,21 +1,102 @@
1
1
  // Copyright 2015-present 650 Industries. All rights reserved.
2
2
 
3
+ import SwiftUI
3
4
  import ExpoModulesCore
4
5
 
5
- public final class RNHostView: ExpoView, ExpoSwiftUI.RNHostViewProtocol {
6
- public required init(appContext: AppContext? = nil) {
7
- super.init(appContext: appContext)
8
- ExpoUITouchHandlerHelper.createAndAttachTouchHandler(for: self)
6
+ internal final class RNHostViewProps: ExpoSwiftUI.ViewProps {
7
+ @Field var matchContents: Bool = false
8
+ }
9
+
10
+ struct RNHostView: ExpoSwiftUI.View {
11
+
12
+ @ObservedObject var props: RNHostViewProps
13
+
14
+ var body: some View {
15
+ if props.matchContents, let childUIView = firstChildUIView {
16
+ ApplySizeFromYogaNode(childUIView: childUIView) {
17
+ Children()
18
+ }
19
+ .onAppear {
20
+ ExpoUITouchHandlerHelper.createAndAttachTouchHandler(for: childUIView)
21
+ }
22
+ } else {
23
+ Children()
24
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
25
+ .modifier(ReportSizeToYogaNodeModifier(shadowNodeProxy: props.shadowNodeProxy))
26
+ .onAppear {
27
+ if let view = firstChildUIView {
28
+ ExpoUITouchHandlerHelper.createAndAttachTouchHandler(for: view)
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ private var firstChildUIView: UIView? {
35
+ props.children?.first?.uiView
36
+ }
37
+ }
38
+
39
+ // Sets SwiftUI view size from Yoga node size
40
+ // Listens to Yoga node size changes and updates the SwiftUI view size
41
+ private struct ApplySizeFromYogaNode<Content: SwiftUI.View>: SwiftUI.View {
42
+ @StateObject private var observer: Observer
43
+ let content: Content
44
+
45
+ init(childUIView: UIView, @ViewBuilder content: () -> Content) {
46
+ _observer = StateObject(wrappedValue: Observer(view: childUIView))
47
+ self.content = content()
9
48
  }
10
49
 
11
- public var matchContents: Bool = false
50
+ var body: some SwiftUI.View {
51
+ content
52
+ .frame(width: observer.size.width, height: observer.size.height)
53
+ }
54
+
55
+ @MainActor
56
+ fileprivate class Observer: ObservableObject {
57
+ @Published var size: CGSize
58
+ private var kvoToken: NSKeyValueObservation?
59
+
60
+ init(view: UIView) {
61
+ self.size = view.bounds.size
62
+ kvoToken = view.observe(\.bounds) { [weak self] view, _ in
63
+ MainActor.assumeIsolated {
64
+ self?.size = view.bounds.size
65
+ }
66
+ }
67
+ }
68
+
69
+ deinit {
70
+ kvoToken?.invalidate()
71
+ }
72
+ }
73
+ }
74
+
75
+ // Sets Yoga node size from SwiftUI view size
76
+ // Listens to SwiftUI view size changes and updates the Yoga node size
77
+ private struct ReportSizeToYogaNodeModifier: ViewModifier {
78
+ let shadowNodeProxy: ExpoSwiftUI.ShadowNodeProxy
79
+
80
+ private func handleSizeChange(_ size: CGSize) {
81
+ shadowNodeProxy.setViewSize?(size)
82
+ }
12
83
 
13
- public override func layoutSubviews() {
14
- super.layoutSubviews()
15
- if matchContents, let subview = self.subviews.first {
16
- self.setViewSize(subview.bounds.size)
84
+ func body(content: Content) -> some View {
85
+ if #available(iOS 16.0, tvOS 16.0, macOS 13.0, *) {
86
+ content.onGeometryChange(for: CGSize.self, of: { proxy in proxy.size }) { size in
87
+ handleSizeChange(size)
88
+ }
17
89
  } else {
18
- self.setViewSize(bounds.size)
90
+ content.overlay {
91
+ GeometryReader { geometry in
92
+ Color.clear
93
+ .hidden()
94
+ .onAppear {
95
+ handleSizeChange(geometry.size)
96
+ }
97
+ .onChange(of: geometry.size) { handleSizeChange($0) }
98
+ }
99
+ }
19
100
  }
20
101
  }
21
102
  }
@@ -95,21 +95,15 @@ internal struct SectionView: ExpoSwiftUI.View {
95
95
  }
96
96
  }
97
97
 
98
- private var contentChildren: SectionContent? {
99
- props.children?
100
- .compactMap({ $0.childView as? SectionContent })
101
- .first
98
+ private var contentChildren: SlotView? {
99
+ props.children?.slot("content")
102
100
  }
103
101
 
104
- private var headerView: SectionHeader? {
105
- props.children?
106
- .compactMap({ $0.childView as? SectionHeader })
107
- .first
102
+ private var headerView: SlotView? {
103
+ props.children?.slot("header")
108
104
  }
109
105
 
110
- private var footerView: SectionFooter? {
111
- props.children?
112
- .compactMap({ $0.childView as? SectionFooter })
113
- .first
106
+ private var footerView: SlotView? {
107
+ props.children?.slot("footer")
114
108
  }
115
109
  }
@@ -46,7 +46,6 @@ struct SecureFieldView: ExpoSwiftUI.View, ExpoSwiftUI.FocusableView {
46
46
  props.placeholder,
47
47
  text: $textManager.text
48
48
  )
49
- .modifier(UIBaseViewModifier(props: props))
50
49
  .fixedSize(horizontal: false, vertical: true)
51
50
  .focused($isFocused)
52
51
  .onSubmit({
@@ -97,7 +97,7 @@ struct ShareLinkView: ExpoSwiftUI.View {
97
97
 
98
98
  var body: some View {
99
99
  #if !os(tvOS)
100
- shareLink.modifier(UIBaseViewModifier(props: props))
100
+ shareLink
101
101
  .onDisappear {
102
102
  // cleanup if unmounted mid async request
103
103
  if #available(iOS 16.0, *) {
@@ -6,6 +6,7 @@ import ExpoModulesCore
6
6
  struct SliderView: ExpoSwiftUI.View {
7
7
  @ObservedObject var props: SliderProps
8
8
  @State var value: Float = 0.0
9
+ @State var isEditing: Bool = false
9
10
 
10
11
  init(props: SliderProps) {
11
12
  self.props = props
@@ -23,8 +24,8 @@ struct SliderView: ExpoSwiftUI.View {
23
24
  }
24
25
  }
25
26
  .onReceive(props.value.publisher, perform: { newValue in
26
- var sliderValue = newValue
27
- value = sliderValue
27
+ guard !isEditing else { return }
28
+ value = newValue
28
29
  })
29
30
  #else
30
31
  Text("Slider is not supported on tvOS")
@@ -34,14 +35,9 @@ struct SliderView: ExpoSwiftUI.View {
34
35
  #if !os(tvOS)
35
36
  @ViewBuilder
36
37
  private var sliderContent: some View {
37
- let label = props.children?.compactMap({ $0.childView as? SliderLabelView })
38
- .first(where: { $0.props.kind == .label })
39
- let minimumValueLabel = props.children?
40
- .compactMap({ $0.childView as? SliderLabelView })
41
- .first(where: { $0.props.kind == .minimum })
42
- let maximumValueLabel = props.children?
43
- .compactMap({ $0.childView as? SliderLabelView })
44
- .first(where: { $0.props.kind == .maximum })
38
+ let label = props.children?.slot("label")
39
+ let minimumValueLabel = props.children?.slot("minimum")
40
+ let maximumValueLabel = props.children?.slot("maximum")
45
41
 
46
42
  if let min = props.min, let max = props.max, let step = props.step {
47
43
  Slider(
@@ -52,6 +48,7 @@ struct SliderView: ExpoSwiftUI.View {
52
48
  minimumValueLabel: { minimumValueLabel },
53
49
  maximumValueLabel: { maximumValueLabel }
54
50
  ) { isEditing in
51
+ self.isEditing = isEditing
55
52
  props.onEditingChanged(["isEditing": isEditing])
56
53
  }
57
54
  } else if let min = props.min, let max = props.max {
@@ -62,6 +59,7 @@ struct SliderView: ExpoSwiftUI.View {
62
59
  minimumValueLabel: { minimumValueLabel },
63
60
  maximumValueLabel: { maximumValueLabel }
64
61
  ) { isEditing in
62
+ self.isEditing = isEditing
65
63
  props.onEditingChanged(["isEditing": isEditing])
66
64
  }
67
65
  } else if let step = props.step {
@@ -73,6 +71,7 @@ struct SliderView: ExpoSwiftUI.View {
73
71
  minimumValueLabel: { minimumValueLabel },
74
72
  maximumValueLabel: { maximumValueLabel }
75
73
  ) { isEditing in
74
+ self.isEditing = isEditing
76
75
  props.onEditingChanged(["isEditing": isEditing])
77
76
  }
78
77
  } else {
@@ -82,6 +81,7 @@ struct SliderView: ExpoSwiftUI.View {
82
81
  minimumValueLabel: { minimumValueLabel },
83
82
  maximumValueLabel: { maximumValueLabel }
84
83
  ) { isEditing in
84
+ self.isEditing = isEditing
85
85
  props.onEditingChanged(["isEditing": isEditing])
86
86
  }
87
87
  }
@@ -98,20 +98,3 @@ final class SliderProps: UIBaseViewProps {
98
98
  var onEditingChanged = EventDispatcher()
99
99
  }
100
100
 
101
- internal enum SliderLabelKind: String, Enumerable {
102
- case label
103
- case minimum
104
- case maximum
105
- }
106
-
107
- internal final class SliderLabelProps: ExpoSwiftUI.ViewProps {
108
- @Field var kind: SliderLabelKind = .minimum
109
- }
110
-
111
- internal struct SliderLabelView: ExpoSwiftUI.View {
112
- @ObservedObject var props: SliderLabelProps
113
-
114
- var body: some View {
115
- Children()
116
- }
117
- }
@@ -0,0 +1,38 @@
1
+ // Copyright 2025-present 650 Industries. All rights reserved.
2
+
3
+ import SwiftUI
4
+ import ExpoModulesCore
5
+
6
+ internal final class SlotViewProps: ExpoSwiftUI.ViewProps {
7
+ @Field var name: String = ""
8
+ }
9
+
10
+ internal struct SlotView: ExpoSwiftUI.View {
11
+ @ObservedObject var props: SlotViewProps
12
+
13
+ init(props: SlotViewProps) {
14
+ self.props = props
15
+ }
16
+
17
+ var body: some View {
18
+ Children()
19
+ }
20
+ }
21
+
22
+ extension [any ExpoSwiftUI.AnyChild] {
23
+ func slot(_ name: String) -> SlotView? {
24
+ compactMap { $0.childView as? SlotView }
25
+ .first { $0.props.name == name }
26
+ }
27
+
28
+ func withoutSlot(_ name: String) -> [any ExpoSwiftUI.AnyChild] {
29
+ filter {
30
+ guard let slot = $0.childView as? SlotView else { return true }
31
+ return slot.props.name != name
32
+ }
33
+ }
34
+
35
+ func withoutSlots() -> [any ExpoSwiftUI.AnyChild] {
36
+ filter { !($0.childView is SlotView) }
37
+ }
38
+ }
@@ -161,7 +161,6 @@ struct TextFieldView: ExpoSwiftUI.View, ExpoSwiftUI.FocusableView {
161
161
  )
162
162
  }
163
163
  return text.lineLimit((props.multiline && allowMultiLine()) ? props.numberOfLines : 1)
164
- .modifier(UIBaseViewModifier(props: props))
165
164
  .fixedSize(horizontal: false, vertical: true)
166
165
  .keyboardType(getKeyboardType(props.keyboardType))
167
166
  .autocorrectionDisabled(!props.autocorrection)