@@ -149,6 +149,15 @@ import androidx.compose.ui.draw.rotate
149149import androidx.compose.ui.draw.scale
150150import androidx.compose.ui.draw.shadow
151151import androidx.compose.ui.graphics.Brush
152+ import androidx.compose.ui.semantics.semantics
153+ import androidx.compose.ui.semantics.clearAndSetSemantics
154+ import androidx.compose.ui.semantics.contentDescription
155+ import androidx.compose.ui.semantics.stateDescription
156+ import androidx.compose.ui.semantics.heading
157+ import androidx.compose.ui.semantics.selected
158+ import androidx.compose.ui.semantics.Role
159+ import androidx.compose.ui.semantics.role
160+ import androidx.compose.ui.platform.testTag
152161import androidx.compose.ui.graphics.Color
153162import androidx.compose.ui.layout.ContentScale
154163import androidx.compose.ui.graphics.ImageBitmap
@@ -1448,6 +1457,44 @@ internal fun ViewNode.composeModifiers(): Modifier {
14481457 }
14491458 }
14501459
1460+ // Accessibility folds into Compose semantics rather than changing
1461+ // any visual property.
1462+ " accessibilityLabel" -> {
1463+ val text = entry.args.string(" text" )
1464+ if (text == null ) modifier else modifier.semantics { contentDescription = text }
1465+ }
1466+
1467+ " accessibilityValue" -> {
1468+ val text = entry.args.string(" text" )
1469+ if (text == null ) modifier else modifier.semantics { stateDescription = text }
1470+ }
1471+
1472+ // clearAndSetSemantics drops this node AND its subtree from the
1473+ // tree, which is what hiding has to mean for a container.
1474+ " accessibilityHidden" -> {
1475+ val hidden = entry.args.string(" value" ) == " true"
1476+ if (hidden) modifier.clearAndSetSemantics { } else modifier
1477+ }
1478+
1479+ " accessibilityAddTraits" -> {
1480+ val traits = (entry.args[" traits" ] as ? kotlinx.serialization.json.JsonArray )
1481+ ?.mapNotNull { (it as ? kotlinx.serialization.json.JsonPrimitive )?.content }
1482+ .orEmpty()
1483+ modifier.semantics {
1484+ if (traits.contains(" button" )) role = Role .Button
1485+ if (traits.contains(" image" )) role = Role .Image
1486+ if (traits.contains(" header" )) heading()
1487+ if (traits.contains(" selected" )) selected = true
1488+ }
1489+ }
1490+
1491+ // A test handle, never announced.
1492+ " accessibilityIdentifier" -> {
1493+ val id = entry.args.string(" id" )
1494+ if (id == null ) modifier else modifier.testTag(id)
1495+ }
1496+
1497+
14511498 // Dim disabled content; controls also drop interactivity via their
14521499 // own `enabled` parameter (e.g. Button).
14531500 " disabled" -> {
@@ -1476,6 +1523,8 @@ private val KNOWN_MODIFIER_KINDS = setOf(
14761523 " tint" , " onAppear" , " onDisappear" , " task" , " onChange" , " animation" , " tag" , " tabItem" ,
14771524 " transition" , " focused" , " longPress" , " drag" , " contentMode" , " progressViewStyle" ,
14781525 " buttonStyle" , " pickerStyle" , " toggleStyle" , " textFieldStyle" ,
1526+ " accessibilityLabel" , " accessibilityValue" , " accessibilityHidden" ,
1527+ " accessibilityAddTraits" , " accessibilityIdentifier" ,
14791528)
14801529
14811530// Folds a frame entry: fixed size, fill (maxWidth/Height .infinity), bounded
0 commit comments