From 6f16edd0d119a27a380b5805e8207409fa687dfe Mon Sep 17 00:00:00 2001 From: UnknownJoe796 Date: Wed, 10 Jun 2026 09:01:03 -0600 Subject: [PATCH] New accessibility style enables lists --- .../kiteui/views/NativeElement.android.kt | 10 ---- .../kiteui/views/direct/modifiers.android.kt | 24 ++++++++ .../kiteui/views/NativeElement.commonHtml.kt | 29 +++------- .../views/direct/IconView.commonHtml.kt | 22 ++++--- .../views/direct/modifiers.commonHtml.kt | 58 ++++++++++++++++++- .../kiteui/models/AccessibleSemantic.kt | 30 ---------- .../com/lightningkite/kiteui/views/Element.kt | 10 ---- .../kiteui/views/NativeElement.kt | 1 - .../kiteui/views/direct/accessibility.kt | 4 +- .../kiteui/views/direct/modifiers.kt | 15 +++++ .../com/lightningkite/kiteui/views/foreach.kt | 21 +++++-- .../kiteui/views/l2/NavigatorView.kt | 3 +- .../kiteui/views/themeDerivations.kt | 6 +- .../kiteui/views/NativeElement.ios.kt | 16 ----- .../kiteui/views/direct/modifiers.ios.kt | 26 +++++++++ .../kotlin/com/lightningkite/kiteui/root.kt | 2 +- package-lock.json | 4 +- 17 files changed, 170 insertions(+), 111 deletions(-) delete mode 100644 library/src/commonMain/kotlin/com/lightningkite/kiteui/models/AccessibleSemantic.kt diff --git a/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/NativeElement.android.kt b/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/NativeElement.android.kt index 613f175c3..e70338023 100644 --- a/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/NativeElement.android.kt +++ b/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/NativeElement.android.kt @@ -27,7 +27,6 @@ import com.lightningkite.kiteui.Log import com.lightningkite.kiteui.OverrideOnly import com.lightningkite.kiteui.afterTimeout import com.lightningkite.kiteui.debugPrint -import com.lightningkite.kiteui.models.AccessibleSemantic import com.lightningkite.kiteui.models.Align import com.lightningkite.kiteui.models.CornerRadii import com.lightningkite.kiteui.models.DragData @@ -119,15 +118,6 @@ actual abstract class NativeElement actual constructor(context: ElementContext) native.contentDescription = value } - override var accessibleSemantic: AccessibleSemantic? - get() = super.accessibleSemantic - set(value) { - super.accessibleSemantic = value - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - native.isAccessibilityHeading = value is AccessibleSemantic.Heading - } - } - override var accessibleLiveRegion: LiveRegionMode get() = super.accessibleLiveRegion set(value) { diff --git a/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/modifiers.android.kt b/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/modifiers.android.kt index 46ee79034..ad0f2083f 100644 --- a/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/modifiers.android.kt +++ b/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/modifiers.android.kt @@ -587,3 +587,27 @@ internal object TypedValueAnimator { } } } + +@ViewModifierDsl3 +actual fun ElementWriter.CanAddTheme.asHeading(level: Int): ElementWriter.CanAddTheme = + beforeSetup { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) native.isAccessibilityHeading = true + } + +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asMain: ElementWriter.CanAddTheme get() = this +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asNavigation: ElementWriter.CanAddTheme get() = this +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asBanner: ElementWriter.CanAddTheme get() = this +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asContentInfo: ElementWriter.CanAddTheme get() = this +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asComplementary: ElementWriter.CanAddTheme get() = this +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asSearch: ElementWriter.CanAddTheme get() = this + +@ViewModifierDsl3 +actual val ElementWriter.CanAddTheme.asPresentation: ElementWriter.CanAddTheme get() = + beforeSetup { native.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS } + +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asList: ElementWriter.CanAddTheme get() = this + +@ViewModifierDsl3 actual val ElementWriter.CanAddAlignment.asListItem: ElementWriter.CanAddAlignment get() = this + +@InternalKiteUi +internal actual fun ContainerElement.setupAsListContainer() {} // TalkBack infers list structure from content diff --git a/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/NativeElement.commonHtml.kt b/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/NativeElement.commonHtml.kt index 25b70a4b4..4576bb477 100644 --- a/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/NativeElement.commonHtml.kt +++ b/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/NativeElement.commonHtml.kt @@ -9,7 +9,6 @@ import com.lightningkite.kiteui.checkLeakAfterDelay import com.lightningkite.kiteui.dom.Event import com.lightningkite.kiteui.models.* import com.lightningkite.kiteui.models.DropTargetDelegate -import com.lightningkite.kiteui.views.NativeElementCommonCode.ThemePipeline private var labelForIdCounter = 0 @@ -58,22 +57,6 @@ actual abstract class NativeElement actual constructor(context: ElementContext) native.setAttribute("aria-label", value) } - override var accessibleSemantic: AccessibleSemantic? - get() = super.accessibleSemantic - set(value) { - super.accessibleSemantic = value - when (value) { - is AccessibleSemantic.Heading -> native.tag = "h${value.level.coerceIn(1, 6)}" - is AccessibleSemantic.Main -> native.tag = "main" - is AccessibleSemantic.Navigation -> native.tag = "nav" - is AccessibleSemantic.Banner -> native.tag = "header" - is AccessibleSemantic.ContentInfo -> native.tag = "footer" - is AccessibleSemantic.Complementary -> native.tag = "aside" - is AccessibleSemantic.Search -> native.tag = "search" - null -> {} // Don't reset tag — we don't know the original - } - } - override var accessibleLiveRegion: LiveRegionMode get() = super.accessibleLiveRegion set(value) { @@ -88,18 +71,22 @@ actual abstract class NativeElement actual constructor(context: ElementContext) override var labelFor: Element? get() = super.labelFor set(value) { + val previous = super.labelFor super.labelFor = value if (value != null) { val targetNative = value.underlyingNativeElement.native if (targetNative.id == null) { targetNative.id = "kiteui-a11y-${labelForIdCounter++}" } - if (native.tag == "span") { + if (native.tag == "span" || native.tag == "p") { native.tag = "label" + native.setAttribute("for", targetNative.id!!) + } else { + targetNative.setAttribute("aria-labelledby", targetNative.id!!) } - native.setAttribute("for", targetNative.id!!) } else { - native.setAttribute("for", null) + if(native.tag == "label") native.setAttribute("for", null) + else previous?.underlyingNativeElement?.native?.setAttribute("aria-labelledby", null) } } @@ -217,6 +204,8 @@ actual abstract class NativeElement actual constructor(context: ElementContext) } } +// TODO: transform this to point to the _STYLE PARTICIPATING_ element, not necessarily the direct element +// That's what we're using it for in every case it's used... val Element.native: FutureElement get() = underlyingNativeElement.native expect class FutureElementStyle diff --git a/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/direct/IconView.commonHtml.kt b/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/direct/IconView.commonHtml.kt index 8c62e7277..61714ce18 100644 --- a/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/direct/IconView.commonHtml.kt +++ b/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/direct/IconView.commonHtml.kt @@ -20,6 +20,7 @@ actual class IconView actual constructor(context: ElementContext) : NativeElemen native.appendChild(FutureElement().apply { tag = "svg" xmlns = "http://www.w3.org/2000/svg" + setAttribute("aria-hidden", "true") style.width = value.width.value.toString() style.height = value.height.value.toString() setStyleProperty("fill", "currentColor") @@ -50,13 +51,20 @@ actual class IconView actual constructor(context: ElementContext) : NativeElemen actual var description: String? = null set(value) { field = value - native.children.firstOrNull()?.let { - it.children.find { it.tag == "title" } - ?.let { it.content = value } - ?: it.appendChild(FutureElement().apply { - tag = "title" - content = value - }) + if (value == "") { + accessibleLabel = null + native.setAttribute("aria-hidden", "true") + } else { + accessibleLabel = value + native.setAttribute("aria-hidden", null) } +// native.children.firstOrNull()?.let { +// it.children.find { it.tag == "title" } +// ?.let { it.content = value } +// ?: it.appendChild(FutureElement().apply { +// tag = "title" +// content = value +// }) +// } } } diff --git a/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/direct/modifiers.commonHtml.kt b/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/direct/modifiers.commonHtml.kt index 2649f8c26..263226389 100644 --- a/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/direct/modifiers.commonHtml.kt +++ b/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/direct/modifiers.commonHtml.kt @@ -5,6 +5,7 @@ package com.lightningkite.kiteui.views.direct import com.lightningkite.kiteui.ExperimentalKiteUi import com.lightningkite.kiteui.InternalKiteUi +import com.lightningkite.kiteui.OverrideOnly import com.lightningkite.kiteui.models.* import com.lightningkite.kiteui.reactive.Action import com.lightningkite.kiteui.views.* @@ -240,4 +241,59 @@ internal expect fun ContainerElement.nativeAnimateHide(transition: ScreenTransit internal expect fun ContainerElement.nativeAnimateWeight(fromWeight: Float, toWeight: Float) @PublishedApi -internal expect fun Element.nativeSetupPullToRefresh(refreshAction: Action) \ No newline at end of file +internal expect fun Element.nativeSetupPullToRefresh(refreshAction: Action) + +private class ApplyTag( + val tag: String, + val wraps: ElementWriter, +) : ViewWriter, ElementWriter by wraps { + + var wrapperElement: NativeContainerElement? = null + + @OverrideOnly + override fun willAddChild(element: Element) { + if (element.native.tag == "span" || element.native.tag == "div") { + wrapperElement = null + element.native.tag = tag + wraps.willAddChild(element) + } else { + val we: NativeContainerElement = PassthroughContainer(wraps.context) + we.native.tag = tag + wraps.willAddChild(we) + we.willAddChild(element) + wrapperElement = we + } + } + + @OverrideOnly + override fun addChild(element: Element) { + wrapperElement?.let { + it.addChild(element) + it.onStartup() + wraps.addChild(it) + } ?: wraps.addChild(element) + } +} + +internal class PassthroughContainer(context: ElementContext): NativeContainerElement(context) { + init { + native.tag = "div" + native.style.display = "block" + } +} + +@ViewModifierDsl3 actual fun ElementWriter.CanAddTheme.asHeading(level: Int): ElementWriter.CanAddTheme = ApplyTag("h${level.coerceIn(1, 6)}", this) +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asMain: ElementWriter.CanAddTheme get() = ApplyTag("main", this) +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asNavigation: ElementWriter.CanAddTheme get() = ApplyTag("nav", this) +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asBanner: ElementWriter.CanAddTheme get() = ApplyTag("header", this) +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asContentInfo: ElementWriter.CanAddTheme get() = ApplyTag("footer", this) +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asComplementary: ElementWriter.CanAddTheme get() = ApplyTag("aside", this) +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asSearch: ElementWriter.CanAddTheme get() = ApplyTag("search", this) +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asPresentation: ElementWriter.CanAddTheme get() = + beforeSetup { native.setAttribute("aria-hidden", "true") } +@ViewModifierDsl3 actual val ElementWriter.CanAddTheme.asList: ElementWriter.CanAddTheme get() = ApplyTag("ul", this) +@ViewModifierDsl3 actual val ElementWriter.CanAddAlignment.asListItem: ElementWriter.CanAddAlignment get() = ApplyTag("li", this) + +internal actual fun ContainerElement.setupAsListContainer() { + if (native.tag == "div" || native.tag == "span") native.tag = "ul" +} diff --git a/library/src/commonMain/kotlin/com/lightningkite/kiteui/models/AccessibleSemantic.kt b/library/src/commonMain/kotlin/com/lightningkite/kiteui/models/AccessibleSemantic.kt deleted file mode 100644 index fa871c4ef..000000000 --- a/library/src/commonMain/kotlin/com/lightningkite/kiteui/models/AccessibleSemantic.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.lightningkite.kiteui.models - -/** - * Semantic role for an element, used by assistive technologies and for HTML semantic elements. - * - * On web, this sets the actual HTML tag (e.g., `

`, `