Skip to content

fix(mix): discover widget-state dependencies through nested variants - #1006

Draft
leoafarias wants to merge 3 commits into
mainfrom
fix/nested-widget-state-discovery
Draft

fix(mix): discover widget-state dependencies through nested variants#1006
leoafarias wants to merge 3 commits into
mainfrom
fix/nested-widget-state-discovery

Conversation

@leoafarias

Copy link
Copy Markdown
Member

Summary

Fixes a silent activation bug in widget-state tracking, extracted from the Tailwind parity work (where dark:hover: combinations exposed it) but fully general.

The bug

Style.widgetStates only scanned top-level WidgetStateVariants. StyleBuilder uses that set to decide whether to attach a MixInteractionDetector, so a style whose only widget-state variants were:

  • nested under another variant — onDark(BoxStyler().onHovered(...)), onBreakpoint(...), or the reverse nesting, or
  • negated — onNot(ContextVariant.widgetState(.hovered), ...)

reported no state dependencies, got no interaction detector, and those variants never activated. No error, no warning — the hover/press/focus style simply did nothing.

The fix

  • Collect dependencies recursively through nested variant styles, with identity-based cycle protection.
  • Introduce ContextVariant.widgetStateDependencies (defaults to empty) so any context variant can declare the widget states it needs tracked: WidgetStateVariant reports its state, NotVariant delegates to its inner variant. This also gives future variants (e.g. a focus-visible variant, coming in the stacked follow-up PR) a first-class way to participate in tracking.

Issue-tracker context

No existing issue reports this. #967 lists Style.widgetStates among its anchors but describes the opposite family of defects (over-subscription and stale inherited styles); this PR addresses the under-tracking side and leaves #967's concerns untouched.

Test plan

  • New widget tests: negated hover, hover nested under breakpoint, hover nested under dark mode, reversed nesting, and a cyclic-structure unit test.
  • Full packages/mix suite on this branch standalone: 2,839 tests pass, dart analyze clean.

Stacked follow-up: #1005's replacement feature PR (Pressable input/semantics rework + focus-visible) is based on this branch.

Style.widgetStates only scanned top-level WidgetStateVariants, so a style
whose only widget-state variants were nested under another variant (for
example onDark or onBreakpoint) or wrapped in NotVariant reported no
dependencies. StyleBuilder then attached no MixInteractionDetector and
those variants never activated.

Collect dependencies recursively with identity-based cycle protection,
and introduce ContextVariant.widgetStateDependencies so any context
variant can declare the widget states it needs tracked. NotVariant
delegates to its inner variant.

Refs #967 (Style.widgetStates is one of its anchors; this addresses the
under-tracking side, not the over-subscription concerns tracked there).
Discovering widget-state dependencies through nested variants made
StyleBuilder mount MixInteractionDetector for states it cannot produce.
The detector only derives hovered/pressed from pointer input; disabled,
focused and the rest must come from an external controller or an ancestor
scope, both of which already bypass it.

Mounting it anyway had two visible costs: its opaque Listener swallowed
pointer events aimed at widgets beneath it, and the state scope it opened
was reused by descendants, so a nested box hovered on its ancestor's
bounds instead of its own.

Also:
- declare the producible set on MixInteractionDetector so the fact lives
  with the code that implements it
- drop the dart:collection import; Set.identity() is in dart:core
- record why the visited set exists: cycles, and onBuilder storing the
  receiver as its placeholder, which is O(2^n) without dedup
- document widgetStateDependencies as the override point for custom
  context variants, and its static-discovery limits
- cover nested pressed, onEnabled discovery, hover exit, and both edges
  of the detector-mounting boundary
@github-actions github-actions Bot added the repo label Aug 6, 2026
…te-discovery-v1

# Conflicts:
#	packages/mix/CHANGELOG.md
@github-actions github-actions Bot removed the repo label Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant