feat: add semantic adapter APIs for visual-layer consumers - #91
Merged
Conversation
- NakedButton.semanticHint on the same Semantics node as the button role, label, enabled state, and tap/long-press actions. - NakedSelect.semanticValue so the trigger announces a human-readable selection instead of T.toString(). The trigger keeps the merged button + expanded + value contract because SemanticsRole.comboBox still fails debug checks (flutter/flutter#172918). - NakedRadioGroup: a thin wrapper over Flutter's RadioGroup supplying what it lacks - a nullable onChanged (null means disabled), a group enabled state radios inherit via a typed NakedRadioGroupScope, and an optional accessible group label. Flutter's RadioGroup keeps the single SemanticsRole.radioGroup node; the label never adds a second role node.
This was referenced Aug 18, 2026
Contributor
LCOV of commit
|
- Hide NakedRadioGroupScope from the public barrel (plumbing, mirrors Flutter's private _RadioGroupStateScope; precedent: hide NakedStateScopeBuilder). - Strengthen the group-label test: assert the labeled container has no role and contains Flutter's radioGroup role node, instead of two independent counts that can't distinguish structure. - Add a mismatched-type nesting test that fails if the enabled scope lookup were untyped (radio must bind to its own-typed, disabled group past a nearer enabled group of another type). - Document the same-type plain-RadioGroup nesting boundary on NakedRadioGroup.
Contributor
LCOV of commit
|
leoafarias
marked this pull request as ready for review
August 19, 2026 00:08
This was referenced Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Three additive APIs that Remix (a visual-only layer over
naked_ui) needs so Naked can be the sole semantics and behavior owner:NakedButton.semanticHint— announced on the same Semantics node as the button role, label, enabled state, and tap/long-press actions. Without it, a consumer that wants a hint must wrap the button in a secondSemanticsnode, which is exactly the duplicate-node pattern a headless library should prevent.NakedSelect.semanticValue— a human-readable value for the current selection. Today the trigger announcesvalue?.toString(), which for any non-primitiveTreads asInstance of 'Foo'. The trigger keeps the merged button + expanded + value contract becauseSemanticsRole.comboBoxstill fails debug semantics checks on stable (Missing checks for role, Missing Combo Box Role Implementation flutter/flutter#172918 — note the TODO insemantics.dartcites #159741, which is closed).NakedRadioGroup<T>— a thin wrapper over Flutter'sRadioGroup.Why bring back a radio group?
A
NakedRadioGroupexisted pre-1.0 and was deliberately dropped when the package hardened for 1.0, delegating grouping to Flutter'sRadioGroup. That delegation stands —NakedRadiostill requires Flutter's registry. This wrapper only supplies whatRadioGroupmeasurably lacks:onChanged. Flutter'sRadioGroup.onChangedis required, so "disabled group" can't be expressed; consumers each invent a no-op adapter.enabled. Radios inherit it via a typedNakedRadioGroupScope<T>, matching Flutter's typedRadioGroup.maybeOf<T>registry lookup so nested groups of different value types can't read each other's enabled state.RadioGrouppublishes theSemanticsRole.radioGroupnode but accepts no label. The label here is a plain labeled container around Flutter's role node — deliberately not a secondradioGrouprole node, which would announce the group twice (verified by test: exactly one role node with or without a label).Tests
semanticHintlives on the single button node (label + hint + tap action on one node).semanticValueannounced instead ofT.toString()for anObjectvalue.NakedRadioGroup: tap selects through grouponChanged;onChanged: nulldisables every radio (semanticsisEnabledfalse);enabled: falseblocks selection; label produces exactly one labeled container and exactly oneradioGrouprole node; no label still leaves Flutter's single role node; nested groups of different types keep enabled state aligned.flutter analyzeclean; 711 tests passed, 3 skipped (Flutter 3.44.0 stable).Consumer
Remix
1.0.0-beta.4(visual-only-over-Naked pass) consumes all three via a temporary path override; compiling Remix against published1.0.0-beta.11fails on exactly these three APIs and nothing else.