feat(mix): rework Pressable input and semantics, add focus-visible variant - #1005
Closed
leoafarias wants to merge 1 commit into
Closed
feat(mix): rework Pressable input and semantics, add focus-visible variant#1005leoafarias wants to merge 1 commit into
leoafarias wants to merge 1 commit into
Conversation
…riant New features: - FocusVisibleVariant, ContextVariant.focusVisible(), and onFocusVisible(), driven by FocusManager.highlightMode through FocusHighlightModeProvider (the same modality signal FocusableActionDetector uses) - ContextVariant.widgetStateDependencies so any context variant can declare the widget states it needs tracked - PressableSemanticsRole (button/link/none) and semanticsLabel Fixes: - Style.widgetStates now discovers state requirements recursively through nested and negated variants (previously a style whose only widget-state variants were nested under e.g. onDark/onBreakpoint, or wrapped in NotVariant, never activated because StyleBuilder attached no MixInteractionDetector) - Pointer pressed state has a single owner (MixInteractionDetector's Listener); the duplicate GestureDetector tapDown/tapUp/tapCancel writers are removed - Keyboard activation models held state: pressed on key down, activate once on key up, repeats suppressed, cancellation on focus loss, disable, controller swap, and dispose - Pressable handles WidgetStatesController swaps in didUpdateWidget and no longer risks disposing an external controller or leaking its own - Semantics expose enabled state, gate tap/longPress on enabled, and no longer duplicate actions through GestureDetector's implicit semantics - PressableBox forwards the full Pressable surface, including the previously dropped enableFeedback BREAKING CHANGE: semanticButtonLabel is renamed to semanticsLabel; the deprecated onKey callback is removed (use onKeyEvent); Pressable reserves Space, Enter, and numpad Enter for activation, so custom actions no longer receive ActivateIntent for those keys. onKeyEvent runs first and can still override activation.
This was referenced Aug 4, 2026
Member
Author
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.
Summary
Interaction-subsystem changes to core
mixextracted from the Tailwind parity branch (leoafarias/mix-tailwind-compat). They were found during parity work, but none of them are Tailwind-specific — they are standalone framework fixes plus two general-purpose features, so they ship as their own PR. The parity branch stacks on top of this one.Bug fixes (real bugs, reproducible without mix_tailwinds)
style.dart):Style.widgetStatesonly scanned top-levelWidgetStateVariants. A style whose only widget-state variants were nested (onDark(BoxStyler().onHovered(...)),onBreakpoint(...)) or negated (onNot(hover)) reported no dependencies, soStyleBuildernever attached aMixInteractionDetectorand the variant never activated. Now recursive with identity-based cycle protection, and driven by the newContextVariant.widgetStateDependenciesso non-WidgetStateVariantvariants (Not, FocusVisible) participate.pressable_widget.dart): bothGestureDetector(tapDown/tapUp/tapCancel) andMixInteractionDetector'sListenerwrotepressedto the same controller, with conflicting timing (e.g. tapCancel during a long-press cleared pressed while the pointer was still down). The Listener is now the single owner.late final _controllerignoredwidget.controllerswaps, anddispose()consulted the current widget to decide ownership — swapping external↔internal could dispose a controller the widget didn't own or leak the one it did. Fixed via_ownsController+didUpdateWidget.onTapeven when disabled, had no enabled state or longPress action, andGestureDetector's implicit semantics duplicated actions. Actions are now gated onenabled,enabledis exposed, and the GestureDetector is always excluded from semantics.PressableBox.enableFeedbackexisted but was never forwarded toPressable; the full surface (cursor, focus, keyboard, controller, actions, semantics) is now forwarded.ActivateIntent→onPresswith no enabled check and no pressed feedback. Now Space/Enter/numpad Enter model held state: pressed on key down, activate once on key up, repeats suppressed, and cancellation on focus loss, disable, controller swap, and dispose.New features
FocusVisibleVariant/ContextVariant.focusVisible()/onFocusVisible(...): CSS:focus-visibleequivalent, keyed offFocusManager.highlightMode(the same input-modality signalFocusableActionDetectoruses), made reactive insidePressablevia the new internalFocusHighlightModeProvider.PressableSemanticsRole(button / link / none) +semanticsLabel.mix_protocol's schema inventory registers the new public symbols as v1 out-of-scope.Breaking changes
semanticButtonLabel→semanticsLabel(no remaining usages in-repo).onKeyremoved; useonKeyEvent(it runs first and can override activation).Pressablereserves Space/Enter/numpad Enter: customactionsno longer receiveActivateIntentfor those keys. Deliberate contract so held-key state stays consistent; documented in the CHANGELOG.Note one judgment call: activation fires on key-up for both Space and Enter (the web fires Enter on key-down). The uniform key-up model was chosen so the held/pressed lifecycle is consistent across keys.
Test plan
packages/mix: full suite passes (2,852 tests), including new coverage: keyboard hold/activate-once/cancel paths, controller-swap state transfer,onKeyEventprecedence, reserved-keys-vs-custom-actions, focus-visible modality switching, semantics role/action matrix, and nested/negated/cyclic variant discovery.packages/mix_protocol: inventory + contract tests pass.dart analyze packages/mix packages/mix_protocol: no issues.