@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
@@ -2,86 +2,240 @@ package expo.modules.ui
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
+ import android.util.Log
6
+ import android.view.MotionEvent
5
7
  import android.view.View
6
8
  import android.view.ViewGroup
7
- import androidx.compose.foundation.rememberScrollState
8
- import androidx.compose.foundation.verticalScroll
9
+ import android.widget.FrameLayout
10
+ import androidx.compose.foundation.layout.fillMaxSize
11
+ import androidx.compose.foundation.layout.requiredSize
9
12
  import androidx.compose.runtime.Composable
13
+ import androidx.compose.runtime.DisposableEffect
10
14
  import androidx.compose.runtime.MutableState
11
15
  import androidx.compose.runtime.mutableStateOf
16
+ import androidx.compose.runtime.remember
12
17
  import androidx.compose.ui.Modifier
18
+ import androidx.compose.ui.layout.onSizeChanged
19
+ import androidx.compose.ui.platform.LocalDensity
20
+ import androidx.compose.ui.unit.IntSize
13
21
  import androidx.compose.ui.viewinterop.AndroidView
14
- import com.facebook.react.ReactRootView
22
+ import com.facebook.react.bridge.ReactContext
23
+ import com.facebook.react.common.annotations.UnstableReactNativeAPI
24
+ import com.facebook.react.config.ReactFeatureFlags
25
+ import com.facebook.react.uimanager.JSPointerDispatcher
26
+ import com.facebook.react.uimanager.JSTouchDispatcher
27
+ import com.facebook.react.uimanager.RootView
28
+ import com.facebook.react.uimanager.ThemedReactContext
29
+ import com.facebook.react.uimanager.UIManagerHelper
30
+ import com.facebook.react.uimanager.events.EventDispatcher
15
31
  import expo.modules.kotlin.AppContext
16
32
  import expo.modules.kotlin.views.ComposableScope
17
33
  import expo.modules.kotlin.views.ComposeProps
18
34
  import expo.modules.kotlin.views.ExpoComposeView
19
35
  import expo.modules.kotlin.views.RNHostViewInterface
20
- import expo.modules.kotlin.views.ShadowNodeProxy
21
- import java.lang.ref.WeakReference
22
36
 
23
- internal data class RNHostProps(
24
- val matchContents: MutableState<Boolean?> = mutableStateOf(null),
25
- val verticalScrollEnabled: MutableState<Boolean?> = mutableStateOf(null),
26
- val modifiers: MutableState<ModifierList> = mutableStateOf(emptyList())
37
+ internal data class RNHostViewProps(
38
+ val matchContents: MutableState<Boolean?> = mutableStateOf(null)
27
39
  ) : ComposeProps
28
40
 
29
41
  @SuppressLint("ViewConstructor")
30
42
  internal class RNHostView(context: Context, appContext: AppContext) :
31
- ExpoComposeView<RNHostProps>(context, appContext), RNHostViewInterface {
32
- override val props = RNHostProps()
33
- override var matchContents: Boolean = false
34
- get() = props.matchContents.value ?: false
43
+ ExpoComposeView<RNHostViewProps>(context, appContext) {
44
+ companion object {
45
+ private const val TAG = "RNHostView"
46
+ }
47
+
48
+ override val props = RNHostViewProps()
49
+
50
+ private val childViewState = mutableStateOf<View?>(null)
51
+ private val wrapperState = mutableStateOf<TouchDispatchingRootViewGroup?>(null)
52
+
53
+ override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
54
+ childViewState.value = child
55
+ val wrapper = TouchDispatchingRootViewGroup(child.context).apply {
56
+ val reactContext = child.context as? ReactContext
57
+ if (reactContext != null) {
58
+ eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, child.id)
59
+ } else {
60
+ Log.e(TAG, "RNHostView: child context is not a ReactContext, touch events will not be dispatched to JS")
61
+ }
62
+ addView(child, FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
63
+ }
64
+ wrapperState.value = wrapper
65
+ }
35
66
 
36
- private val container = RNHostContainerView(context, WeakReference(shadowNodeProxy))
67
+ override fun removeView(view: View) {
68
+ if (view == childViewState.value) {
69
+ wrapperState.value?.removeView(view)
70
+ childViewState.value = null
71
+ wrapperState.value = null
72
+ } else {
73
+ super.removeView(view)
74
+ }
75
+ }
76
+
77
+ override fun removeViewAt(index: Int) {
78
+ childViewState.value?.let { child ->
79
+ wrapperState.value?.removeView(child)
80
+ }
81
+ childViewState.value = null
82
+ wrapperState.value = null
83
+ }
37
84
 
38
85
  @Composable
39
86
  override fun ComposableScope.Content() {
40
- val (verticalScrollEnabled) = props.verticalScrollEnabled
41
- val (modifiers) = props.modifiers
42
-
43
- AndroidView(
44
- factory = {
45
- container
46
- },
47
- modifier = ModifierRegistry.applyModifiers(modifiers, appContext, this@Content, globalEventDispatcher)
48
- .then(if (verticalScrollEnabled == true) Modifier.verticalScroll(rememberScrollState()) else Modifier)
49
- )
87
+ val matchContents = props.matchContents.value ?: false
88
+
89
+ wrapperState.value?.let { wrapper ->
90
+ val childView = childViewState.value ?: return@let
91
+ if (matchContents) {
92
+ AndroidView(
93
+ factory = { wrapper },
94
+ modifier = applySizeFromYogaNodeModifier(childView)
95
+ )
96
+ } else {
97
+ AndroidView(
98
+ factory = { wrapper },
99
+ modifier = Modifier
100
+ .fillMaxSize()
101
+ .then(reportSizeToYogaNodeModifier())
102
+ )
103
+ }
104
+ }
50
105
  }
51
106
 
52
- override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
53
- container.addView(child, index, params)
107
+ // Sets Compose view size from Yoga node size
108
+ // Listens yoga node size changes and updates the Compose view size
109
+ @Composable
110
+ private fun applySizeFromYogaNodeModifier(childView: View): Modifier {
111
+ val density = LocalDensity.current
112
+
113
+ val childSize = remember {
114
+ mutableStateOf(
115
+ if (childView.width > 0 && childView.height > 0) IntSize(childView.width, childView.height)
116
+ else IntSize.Zero
117
+ )
118
+ }
119
+
120
+ DisposableEffect(childView) {
121
+ val listener = View.OnLayoutChangeListener { _, l, t, r, b, _, _, _, _ ->
122
+ childSize.value = IntSize(r - l, b - t)
123
+ }
124
+ childView.addOnLayoutChangeListener(listener)
125
+ onDispose { childView.removeOnLayoutChangeListener(listener) }
126
+ }
127
+
128
+ return with(density) {
129
+ if (childSize.value.width > 0 && childSize.value.height > 0) {
130
+ Modifier.requiredSize(
131
+ childSize.value.width.toDp(),
132
+ childSize.value.height.toDp()
133
+ )
134
+ } else {
135
+ Modifier
136
+ }
137
+ }
54
138
  }
55
139
 
56
- override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
57
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
58
- container.measure(widthMeasureSpec, heightMeasureSpec)
59
- }
60
-
61
- override fun onLayout(
62
- changed: Boolean,
63
- left: Int,
64
- top: Int,
65
- right: Int,
66
- bottom: Int
67
- ) {
68
- super.onLayout(changed, left, top, right, bottom)
69
- val offsetX = paddingLeft
70
- val offsetY = paddingRight
71
- container.layout(offsetX, offsetY, offsetX + width, offsetY + height)
140
+ // Sets Yoga node size from Compose view size
141
+ // Listens Compose view size changes and updates the Yoga node size
142
+ @Composable
143
+ private fun reportSizeToYogaNodeModifier(): Modifier {
144
+ val density = LocalDensity.current
145
+ return Modifier.onSizeChanged { size ->
146
+ with(density) {
147
+ shadowNodeProxy.setViewSize(
148
+ size.width.toDp().value.toDouble(),
149
+ size.height.toDp().value.toDouble()
150
+ )
151
+ }
152
+ }
72
153
  }
73
154
  }
74
155
 
75
- internal class RNHostContainerView(context: Context, private val shadowNodeProxy: WeakReference<ShadowNodeProxy>) : ReactRootView(context) {
76
- var matchContents = false
156
+ /**
157
+ * A thin FrameLayout that intercepts touch events and dispatches them to JS via
158
+ * JSTouchDispatcher/JSPointerDispatcher, replicating the pattern from React Native's
159
+ * DialogRootViewGroup in ReactModalHostView.
160
+ */
161
+ private class TouchDispatchingRootViewGroup(
162
+ context: Context
163
+ ) : FrameLayout(context), RootView {
164
+ private val jsTouchDispatcher = JSTouchDispatcher(this)
165
+ private var jsPointerDispatcher: JSPointerDispatcher? = null
166
+
167
+ var eventDispatcher: EventDispatcher? = null
168
+
169
+ private val reactContext: ThemedReactContext
170
+ get() = context as ThemedReactContext
171
+
172
+ init {
173
+ if (ReactFeatureFlags.dispatchPointerEvents) {
174
+ jsPointerDispatcher = JSPointerDispatcher(this)
175
+ }
176
+ }
177
+
178
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
179
+ // Always gets called with EXACTLY mode
180
+ // because parent has either fillMaxSize (matchContents = false) or requiredSize (matchContents = true) modifiers
181
+ setMeasuredDimension(
182
+ MeasureSpec.getSize(widthMeasureSpec),
183
+ MeasureSpec.getSize(heightMeasureSpec)
184
+ )
185
+ }
77
186
 
78
187
  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
79
- super.onLayout(changed, left, top, right, bottom)
80
- if (matchContents && childCount > 1) {
81
- val subview = getChildAt(0)
82
- shadowNodeProxy.get()?.setViewSize(subview.width.toDouble(), subview.height.toDouble())
83
- } else {
84
- shadowNodeProxy.get()?.setViewSize(width.toDouble(), height.toDouble())
188
+ // No-op: don't re-layout children. Yoga calls child.layout() directly
189
+ // and we must not override those values.
190
+ }
191
+
192
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
193
+ eventDispatcher?.let { dispatcher ->
194
+ jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
195
+ jsPointerDispatcher?.handleMotionEvent(event, dispatcher, true)
196
+ }
197
+ return super.onInterceptTouchEvent(event)
198
+ }
199
+
200
+ @SuppressLint("ClickableViewAccessibility")
201
+ override fun onTouchEvent(event: MotionEvent): Boolean {
202
+ eventDispatcher?.let { dispatcher ->
203
+ jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
204
+ jsPointerDispatcher?.handleMotionEvent(event, dispatcher, false)
85
205
  }
206
+ super.onTouchEvent(event)
207
+ return true
208
+ }
209
+
210
+ override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
211
+ eventDispatcher?.let { jsPointerDispatcher?.handleMotionEvent(event, it, true) }
212
+ return super.onInterceptHoverEvent(event)
213
+ }
214
+
215
+ override fun onHoverEvent(event: MotionEvent): Boolean {
216
+ eventDispatcher?.let { jsPointerDispatcher?.handleMotionEvent(event, it, false) }
217
+ return super.onHoverEvent(event)
218
+ }
219
+
220
+ @OptIn(UnstableReactNativeAPI::class)
221
+ override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
222
+ eventDispatcher?.let { dispatcher ->
223
+ jsTouchDispatcher.onChildStartedNativeGesture(ev, dispatcher, reactContext)
224
+ jsPointerDispatcher?.onChildStartedNativeGesture(childView, ev, dispatcher)
225
+ }
226
+ }
227
+
228
+ override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
229
+ eventDispatcher?.let { jsTouchDispatcher.onChildEndedNativeGesture(ev, it) }
230
+ jsPointerDispatcher?.onChildEndedNativeGesture()
231
+ }
232
+
233
+ override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
234
+ // No-op - override to still receive events in onInterceptTouchEvent
235
+ // even when a child view disallows interception
236
+ }
237
+
238
+ override fun handleException(t: Throwable) {
239
+ reactContext.reactApplicationContext.handleException(RuntimeException(t))
86
240
  }
87
241
  }
@@ -25,12 +25,48 @@ class SwitchColors : Record {
25
25
  @Field
26
26
  val checkedTrackColor: Color? = null
27
27
 
28
+ @Field
29
+ val checkedBorderColor: Color? = null
30
+
31
+ @Field
32
+ val checkedIconColor: Color? = null
33
+
28
34
  @Field
29
35
  val uncheckedThumbColor: Color? = null
30
36
 
31
37
  @Field
32
38
  val uncheckedTrackColor: Color? = null
33
39
 
40
+ @Field
41
+ val uncheckedBorderColor: Color? = null
42
+
43
+ @Field
44
+ val uncheckedIconColor: Color? = null
45
+
46
+ @Field
47
+ val disabledCheckedThumbColor: Color? = null
48
+
49
+ @Field
50
+ val disabledCheckedTrackColor: Color? = null
51
+
52
+ @Field
53
+ val disabledCheckedBorderColor: Color? = null
54
+
55
+ @Field
56
+ val disabledCheckedIconColor: Color? = null
57
+
58
+ @Field
59
+ val disabledUncheckedThumbColor: Color? = null
60
+
61
+ @Field
62
+ val disabledUncheckedTrackColor: Color? = null
63
+
64
+ @Field
65
+ val disabledUncheckedBorderColor: Color? = null
66
+
67
+ @Field
68
+ val disabledUncheckedIconColor: Color? = null
69
+
34
70
  @Field
35
71
  val checkedColor: Color? = null
36
72
 
@@ -52,6 +88,7 @@ class SwitchColors : Record {
52
88
 
53
89
  data class SwitchProps(
54
90
  val value: Boolean = false,
91
+ val enabled: Boolean = true,
55
92
  val variant: String = "switch",
56
93
  val elementColors: SwitchColors = SwitchColors(),
57
94
  val modifiers: ModifierList = emptyList()
@@ -63,12 +100,14 @@ fun SwitchComposable(
63
100
  onCheckedChange: ((Boolean) -> Unit)?,
64
101
  colors: SwitchColors,
65
102
  modifier: Modifier = Modifier,
103
+ enabled: Boolean = true,
66
104
  thumbContent: (@Composable () -> Unit)? = null
67
105
  ) {
68
106
  Switch(
69
107
  checked = checked,
70
108
  onCheckedChange = onCheckedChange,
71
109
  modifier = modifier,
110
+ enabled = enabled,
72
111
  thumbContent = thumbContent,
73
112
  colors = SwitchDefaults.colors(
74
113
  // For some reason the default way of passing colors using `compose` results in a transparent view
@@ -76,20 +115,45 @@ fun SwitchComposable(
76
115
  ?: SwitchDefaults.colors().checkedThumbColor,
77
116
  checkedTrackColor = colors.checkedTrackColor.composeOrNull
78
117
  ?: SwitchDefaults.colors().checkedTrackColor,
118
+ checkedBorderColor = colors.checkedBorderColor.composeOrNull
119
+ ?: SwitchDefaults.colors().checkedBorderColor,
120
+ checkedIconColor = colors.checkedIconColor.composeOrNull
121
+ ?: SwitchDefaults.colors().checkedIconColor,
79
122
  uncheckedThumbColor = colors.uncheckedThumbColor.composeOrNull
80
123
  ?: SwitchDefaults.colors().uncheckedThumbColor,
81
124
  uncheckedTrackColor = colors.uncheckedTrackColor.composeOrNull
82
- ?: SwitchDefaults.colors().uncheckedTrackColor
125
+ ?: SwitchDefaults.colors().uncheckedTrackColor,
126
+ uncheckedBorderColor = colors.uncheckedBorderColor.composeOrNull
127
+ ?: SwitchDefaults.colors().uncheckedBorderColor,
128
+ uncheckedIconColor = colors.uncheckedIconColor.composeOrNull
129
+ ?: SwitchDefaults.colors().uncheckedIconColor,
130
+ disabledCheckedBorderColor = colors.disabledCheckedBorderColor.composeOrNull
131
+ ?: SwitchDefaults.colors().disabledCheckedBorderColor,
132
+ disabledCheckedThumbColor = colors.disabledCheckedThumbColor.composeOrNull
133
+ ?: SwitchDefaults.colors().disabledCheckedThumbColor,
134
+ disabledCheckedTrackColor = colors.disabledCheckedTrackColor.composeOrNull
135
+ ?: SwitchDefaults.colors().disabledCheckedTrackColor,
136
+ disabledCheckedIconColor = colors.disabledCheckedIconColor.composeOrNull
137
+ ?: SwitchDefaults.colors().disabledCheckedIconColor,
138
+ disabledUncheckedBorderColor = colors.disabledUncheckedBorderColor.composeOrNull
139
+ ?: SwitchDefaults.colors().disabledUncheckedBorderColor,
140
+ disabledUncheckedThumbColor = colors.disabledUncheckedThumbColor.composeOrNull
141
+ ?: SwitchDefaults.colors().disabledUncheckedThumbColor,
142
+ disabledUncheckedTrackColor = colors.disabledUncheckedTrackColor.composeOrNull
143
+ ?: SwitchDefaults.colors().disabledUncheckedTrackColor,
144
+ disabledUncheckedIconColor = colors.disabledUncheckedIconColor.composeOrNull
145
+ ?: SwitchDefaults.colors().disabledUncheckedIconColor
83
146
  )
84
147
  )
85
148
  }
86
149
 
87
150
  @Composable
88
- fun CheckboxComposable(checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, colors: SwitchColors, modifier: Modifier) {
151
+ fun CheckboxComposable(checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, colors: SwitchColors, modifier: Modifier, enabled: Boolean = true) {
89
152
  Checkbox(
90
153
  checked = checked,
91
154
  onCheckedChange = onCheckedChange,
92
155
  modifier = modifier,
156
+ enabled = enabled,
93
157
  colors = CheckboxDefaults.colors(
94
158
  checkedColor = colors.checkedColor.compose,
95
159
  disabledCheckedColor = colors.disabledCheckedColor.compose,
@@ -108,11 +172,12 @@ fun ThemedHybridSwitch(
108
172
  onCheckedChange: ((Boolean) -> Unit)?,
109
173
  colors: SwitchColors,
110
174
  modifier: Modifier = Modifier,
175
+ enabled: Boolean = true,
111
176
  thumbContent: (@Composable () -> Unit)? = null
112
177
  ) {
113
178
  when (variant) {
114
- "switch" -> SwitchComposable(checked, onCheckedChange, colors, modifier, thumbContent)
115
- else -> CheckboxComposable(checked, onCheckedChange, colors, modifier)
179
+ "switch" -> SwitchComposable(checked, onCheckedChange, colors, modifier, enabled, thumbContent)
180
+ else -> CheckboxComposable(checked, onCheckedChange, colors, modifier, enabled)
116
181
  }
117
182
  }
118
183
 
@@ -129,6 +194,7 @@ fun FunctionalComposableScope.SwitchContent(
129
194
  { newChecked -> onValueChange(ValueChangeEvent(newChecked)) },
130
195
  props.elementColors,
131
196
  ModifierRegistry.applyModifiers(props.modifiers, appContext, composableScope, globalEventDispatcher),
197
+ props.enabled,
132
198
  thumbContent = thumbContentSlotView?.let {
133
199
  {
134
200
  with(ComposableScope()) {
@@ -16,6 +16,7 @@ import expo.modules.ui.ModifierList
16
16
  import expo.modules.ui.ModifierRegistry
17
17
  import expo.modules.ui.ShapeRecord
18
18
  import expo.modules.ui.compose
19
+ import expo.modules.ui.menu.LocalContextMenuExpanded
19
20
  import expo.modules.ui.shapeFromShapeRecord
20
21
 
21
22
  enum class IconButtonVariant(val value: String) : Enumerable {
@@ -95,11 +96,16 @@ fun FunctionalComposableScope.IconButtonContent(
95
96
  val colors = props.elementColors
96
97
  val disabled = props.disabled
97
98
 
99
+ val contextMenuExpanded = LocalContextMenuExpanded.current
100
+
98
101
  StyledIconButton(
99
102
  variant ?: IconButtonVariant.DEFAULT,
100
103
  colors,
101
104
  disabled ?: false,
102
- onPress = { onButtonPressed(ButtonPressedEvent()) },
105
+ onPress = {
106
+ contextMenuExpanded?.let { it.value = true }
107
+ onButtonPressed(ButtonPressedEvent())
108
+ },
103
109
  modifier = ModifierRegistry.applyModifiers(props.modifiers, appContext, composableScope, globalEventDispatcher),
104
110
  shape = shapeFromShapeRecord(props.shape)
105
111
  ) {
@@ -0,0 +1,26 @@
1
+ package expo.modules.ui.convertibles
2
+
3
+ import androidx.compose.animation.core.AnimationSpec
4
+ import androidx.compose.animation.core.animateFloatAsState
5
+ import androidx.compose.animation.core.snap
6
+ import androidx.compose.animation.core.spring
7
+ import androidx.compose.runtime.Composable
8
+ import androidx.compose.runtime.getValue
9
+
10
+ @Composable
11
+ fun resolveAnimatable(map: Map<String, Any?>, key: String, default: Float): Float {
12
+ val raw = map[key]
13
+ val targetValue = when {
14
+ raw is Number -> raw.toFloat()
15
+ raw is Map<*, *> && raw["\$animated"] == true ->
16
+ (raw["targetValue"] as Number).toFloat()
17
+ else -> default
18
+ }
19
+ val spec: AnimationSpec<Float> = when {
20
+ raw is Map<*, *> && raw["\$animated"] == true ->
21
+ parseAnimationSpec(raw["animationSpec"]) ?: spring()
22
+ else -> snap()
23
+ }
24
+ val animated by animateFloatAsState(targetValue, spec, label = key)
25
+ return animated
26
+ }
@@ -0,0 +1,93 @@
1
+ package expo.modules.ui.convertibles
2
+
3
+ import androidx.compose.animation.core.AnimationSpec
4
+ import androidx.compose.animation.core.EaseInOut
5
+ import androidx.compose.animation.core.FastOutLinearInEasing
6
+ import androidx.compose.animation.core.FastOutSlowInEasing
7
+ import androidx.compose.animation.core.LinearEasing
8
+ import androidx.compose.animation.core.LinearOutSlowInEasing
9
+ import androidx.compose.animation.core.Spring
10
+ import androidx.compose.animation.core.keyframes
11
+ import androidx.compose.animation.core.snap
12
+ import androidx.compose.animation.core.spring
13
+ import androidx.compose.animation.core.tween
14
+ import expo.modules.kotlin.records.Field
15
+ import expo.modules.kotlin.records.Record
16
+ import expo.modules.kotlin.records.recordFromMap
17
+ import expo.modules.kotlin.types.Enumerable
18
+
19
+ internal enum class EasingType(val value: String) : Enumerable {
20
+ LINEAR("linear"),
21
+ FAST_OUT_SLOW_IN("fastOutSlowIn"),
22
+ FAST_OUT_LINEAR_IN("fastOutLinearIn"),
23
+ LINEAR_OUT_SLOW_IN("linearOutSlowIn"),
24
+ EASE("ease");
25
+
26
+ fun toEasing() = when (this) {
27
+ LINEAR -> LinearEasing
28
+ FAST_OUT_SLOW_IN -> FastOutSlowInEasing
29
+ FAST_OUT_LINEAR_IN -> FastOutLinearInEasing
30
+ LINEAR_OUT_SLOW_IN -> LinearOutSlowInEasing
31
+ EASE -> EaseInOut
32
+ }
33
+ }
34
+
35
+ internal data class SpringSpecParams(
36
+ @Field val dampingRatio: Float = Spring.DampingRatioNoBouncy,
37
+ @Field val stiffness: Float = Spring.StiffnessMedium,
38
+ @Field val visibilityThreshold: Float? = null
39
+ ) : Record {
40
+ fun toAnimationSpec(): AnimationSpec<Float> = spring(
41
+ dampingRatio = dampingRatio,
42
+ stiffness = stiffness,
43
+ visibilityThreshold = visibilityThreshold
44
+ )
45
+ }
46
+
47
+ internal data class TweenSpecParams(
48
+ @Field val durationMillis: Int = 300,
49
+ @Field val delayMillis: Int = 0,
50
+ @Field val easing: EasingType? = null
51
+ ) : Record {
52
+ fun toAnimationSpec(): AnimationSpec<Float> = tween(
53
+ durationMillis = durationMillis,
54
+ delayMillis = delayMillis,
55
+ easing = easing?.toEasing() ?: FastOutSlowInEasing
56
+ )
57
+ }
58
+
59
+ internal data class SnapSpecParams(
60
+ @Field val delayMillis: Int = 0
61
+ ) : Record {
62
+ fun toAnimationSpec(): AnimationSpec<Float> = snap(delayMillis = delayMillis)
63
+ }
64
+
65
+ internal data class KeyframesSpecParams(
66
+ @Field val durationMillis: Int = 300,
67
+ @Field val delayMillis: Int = 0
68
+ ) : Record {
69
+ @Suppress("UNCHECKED_CAST")
70
+ fun toAnimationSpec(raw: Map<*, *>): AnimationSpec<Float> {
71
+ val kf = raw["keyframes"] as? Map<String, Number> ?: emptyMap()
72
+ return keyframes {
73
+ this.durationMillis = this@KeyframesSpecParams.durationMillis
74
+ this.delayMillis = this@KeyframesSpecParams.delayMillis
75
+ for ((time, value) in kf) {
76
+ value.toFloat() at time.toInt()
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ @Suppress("UNCHECKED_CAST")
83
+ internal fun parseAnimationSpec(raw: Any?): AnimationSpec<Float>? {
84
+ if (raw !is Map<*, *>) return null
85
+ val map = raw as Map<String, Any?>
86
+ return when (raw["\$type"]) {
87
+ "spring" -> recordFromMap<SpringSpecParams>(map).toAnimationSpec()
88
+ "tween" -> recordFromMap<TweenSpecParams>(map).toAnimationSpec()
89
+ "snap" -> recordFromMap<SnapSpecParams>(map).toAnimationSpec()
90
+ "keyframes" -> recordFromMap<KeyframesSpecParams>(map).toAnimationSpec(raw)
91
+ else -> null
92
+ }
93
+ }
@@ -0,0 +1,24 @@
1
+ package expo.modules.ui.convertibles
2
+
3
+ import android.graphics.Color
4
+ import expo.modules.kotlin.records.Field
5
+ import expo.modules.kotlin.records.Record
6
+ import expo.modules.kotlin.types.Enumerable
7
+ import expo.modules.ui.BuiltinShapeRecord
8
+
9
+ enum class CompositingStrategyType(val value: String) : Enumerable {
10
+ AUTO("auto"),
11
+ OFFSCREEN("offscreen"),
12
+ MODULATE("modulate")
13
+ }
14
+
15
+ internal data class GraphicsLayerParams(
16
+ @Field val cameraDistance: Float = 8f,
17
+ @Field val transformOriginX: Float = 0.5f,
18
+ @Field val transformOriginY: Float = 0.5f,
19
+ @Field val clip: Boolean = false,
20
+ @Field val shape: BuiltinShapeRecord? = null,
21
+ @Field val ambientShadowColor: Color? = null,
22
+ @Field val spotShadowColor: Color? = null,
23
+ @Field val compositingStrategy: CompositingStrategyType? = null
24
+ ) : Record
@@ -0,0 +1,14 @@
1
+ import { ContentAlignment, FloatingToolbarExitAlwaysScrollBehavior, PrimitiveBaseProps } from '../layout-types';
2
+ export type BoxProps = {
3
+ children?: React.ReactNode;
4
+ /**
5
+ * Alignment of children within the box.
6
+ */
7
+ contentAlignment?: ContentAlignment;
8
+ /**
9
+ * Scroll behavior for the floating toolbar exit.
10
+ */
11
+ floatingToolbarExitAlwaysScrollBehavior?: FloatingToolbarExitAlwaysScrollBehavior;
12
+ } & PrimitiveBaseProps;
13
+ export declare function Box(props: BoxProps): import("react").JSX.Element;
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/jetpack-compose/Box/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,gBAAgB,EAChB,uCAAuC,EACvC,kBAAkB,EAEnB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B;;OAEG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC;;OAEG;IACH,uCAAuC,CAAC,EAAE,uCAAuC,CAAC;CACnF,GAAG,kBAAkB,CAAC;AAIvB,wBAAgB,GAAG,CAAC,KAAK,EAAE,QAAQ,+BAElC"}
@@ -0,0 +1,22 @@
1
+ import { HorizontalAlignment, HorizontalArrangement, PrimitiveBaseProps, VerticalAlignment, VerticalArrangement } from '../layout-types';
2
+ export type ColumnProps = {
3
+ children?: React.ReactNode;
4
+ /**
5
+ * Horizontal arrangement of children.
6
+ */
7
+ horizontalArrangement?: HorizontalArrangement;
8
+ /**
9
+ * Vertical arrangement of children.
10
+ */
11
+ verticalArrangement?: VerticalArrangement;
12
+ /**
13
+ * Horizontal alignment of children.
14
+ */
15
+ horizontalAlignment?: HorizontalAlignment;
16
+ /**
17
+ * Vertical alignment of children.
18
+ */
19
+ verticalAlignment?: VerticalAlignment;
20
+ } & PrimitiveBaseProps;
21
+ export declare function Column(props: ColumnProps): import("react").JSX.Element;
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/jetpack-compose/Column/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EAEpB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B;;OAEG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C;;OAEG;IACH,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C;;OAEG;IACH,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C;;OAEG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC,GAAG,kBAAkB,CAAC;AAOvB,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,+BAExC"}