Skip to content

Commit d57d94a

Browse files
committed
Give the composable registry typed props and a reliable child slot
1 parent b1f7ae0 commit d57d94a

1 file changed

Lines changed: 31 additions & 13 deletions

File tree

Demo/swiftui/src/commonMain/kotlin/com/pureswift/swiftui/ComposableRegistry.kt

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,36 @@ import androidx.compose.material3.Text
44
import androidx.compose.runtime.Composable
55
import androidx.compose.ui.graphics.Color
66
import kotlinx.serialization.json.JsonObject
7+
import kotlinx.serialization.json.JsonPrimitive
8+
import kotlinx.serialization.json.booleanOrNull
9+
import kotlinx.serialization.json.doubleOrNull
10+
import kotlinx.serialization.json.intOrNull
11+
import kotlinx.serialization.json.longOrNull
12+
13+
/// Typed access to a custom composable's props, so app authors read values
14+
/// without touching the underlying JSON layer (or depending on it).
15+
class Props internal constructor(private val json: JsonObject) {
16+
fun string(key: String): String? = (json[key] as? JsonPrimitive)?.content
17+
fun double(key: String): Double? = (json[key] as? JsonPrimitive)?.doubleOrNull
18+
fun float(key: String): Float? = double(key)?.toFloat()
19+
fun int(key: String): Int? = (json[key] as? JsonPrimitive)?.intOrNull
20+
fun bool(key: String): Boolean? = (json[key] as? JsonPrimitive)?.booleanOrNull
21+
/// A color passed from Swift as `PropValue.color(_:)` (an ARGB int).
22+
fun color(key: String): Color? = (json[key] as? JsonPrimitive)?.longOrNull?.let { Color(it.toInt()) }
23+
}
724

825
/// The library's single extension point: Kotlin registers named composables;
9-
/// Swift emits a `Composable(name:props:)` node referencing one. A registered
10-
/// factory receives the node's props and its rendered children slot. Entries
11-
/// registered from common code work on both Android and desktop; a factory
12-
/// that wraps an Android-only view simply isn't registered on desktop.
26+
/// Swift emits a `ComposableView(name:props:)` node referencing one. A factory
27+
/// receives typed props and a slot rendering the SwiftUI child content. Entries
28+
/// registered from common code work on both Android and desktop; a factory that
29+
/// wraps an Android-only view simply isn't registered on desktop.
1330
object ComposableRegistry {
1431

15-
fun interface Factory {
16-
@Composable
17-
fun Content(props: JsonObject, children: @Composable () -> Unit)
18-
}
19-
20-
private val factories = mutableMapOf<String, Factory>()
32+
// A registered factory: given props and a slot rendering the SwiftUI child
33+
// content, it emits composables. Stored as a plain composable function type.
34+
private val factories = mutableMapOf<String, @Composable (props: Props, children: @Composable () -> Unit) -> Unit>()
2135

22-
fun register(name: String, factory: Factory) {
36+
fun register(name: String, factory: @Composable (props: Props, children: @Composable () -> Unit) -> Unit) {
2337
factories[name] = factory
2438
}
2539

@@ -28,9 +42,13 @@ object ComposableRegistry {
2842
val name = node.string("name") ?: return
2943
val factory = factories[name]
3044
if (factory != null) {
31-
factory.Content(node.props) {
45+
factory(Props(node.props)) {
46+
// RenderChild, not the public Render: a nested call to a public
47+
// composable from this dynamically-invoked factory slot has its
48+
// restart group skipped by the runtime and silently renders
49+
// nothing.
3250
for (child in node.children) {
33-
Render(child)
51+
RenderChild(child)
3452
}
3553
}
3654
} else {

0 commit comments

Comments
 (0)