@@ -1243,13 +1243,55 @@ private fun RenderNavStack(node: ViewNode) {
12431243 label = " nav" ,
12441244 ) { index ->
12451245 LayoutColumn (modifier = Modifier .padding(padding)) {
1246+ node.searchField(index)?.let { RenderSearchField (it, index) }
12461247 node.children.getOrNull(index)?.let { Render (it) }
12471248 }
12481249 }
12491250 }
12501251 RenderSheetsAndAlerts (node.children.getOrNull(topIndex))
12511252}
12521253
1254+ private class SearchFieldSpec (val text : String , val callbackId : Long , val prompt : String )
1255+
1256+ // A screen's `searchable` descriptor: [text, callbackId, prompt], or an empty
1257+ // array when that screen declared none.
1258+ private fun ViewNode.searchField (index : Int ): SearchFieldSpec ? {
1259+ val searches = props[" searches" ] as ? kotlinx.serialization.json.JsonArray ? : return null
1260+ val entry = searches.getOrNull(index) as ? kotlinx.serialization.json.JsonArray ? : return null
1261+ if (entry.size < 3 ) return null
1262+ val text = (entry[0 ] as ? kotlinx.serialization.json.JsonPrimitive )?.content ? : " "
1263+ val id = (entry[1 ] as ? kotlinx.serialization.json.JsonPrimitive )?.content?.toLongOrNull() ? : return null
1264+ val prompt = (entry[2 ] as ? kotlinx.serialization.json.JsonPrimitive )?.content ? : " "
1265+ return SearchFieldSpec (text, id, prompt)
1266+ }
1267+
1268+ // Same uncontrolled-with-reconciliation scheme as RenderTextField, keyed by the
1269+ // stable screen index (the callback id churns each evaluation, so it can't key
1270+ // the remembered cursor state).
1271+ @Composable
1272+ private fun RenderSearchField (spec : SearchFieldSpec , index : Int ) {
1273+ var local by remember(index) { mutableStateOf(TextFieldValue (spec.text)) }
1274+ var lastSent by remember(index) { mutableStateOf(spec.text) }
1275+ if (spec.text != lastSent) {
1276+ local = TextFieldValue (spec.text, selection = androidx.compose.ui.text.TextRange (spec.text.length))
1277+ lastSent = spec.text
1278+ }
1279+ OutlinedTextField (
1280+ value = local,
1281+ onValueChange = { v ->
1282+ local = v
1283+ if (v.text != lastSent) {
1284+ lastSent = v.text
1285+ SwiftBridge .sink.invokeString(spec.callbackId, v.text)
1286+ }
1287+ },
1288+ placeholder = { Text (spec.prompt.ifEmpty { " Search" }) },
1289+ leadingIcon = { Icon (Icons .Filled .Search , contentDescription = null ) },
1290+ singleLine = true ,
1291+ modifier = Modifier .fillMaxWidth().padding(horizontal = 12 .dp, vertical = 4 .dp),
1292+ )
1293+ }
1294+
12531295@Composable
12541296private fun RenderTabView (node : ViewNode ) {
12551297 val selection = node.long(" selection" )?.toInt() ? : 0
0 commit comments