Problem/Motivation

Drupal's client-side States API (`core/misc/states.js`) has no concept of "form scope." When a #states selector matches elements belonging to more than one independent `
` on the same page — which happens whenever the same field name is legitimately reused across page instances, for example the same View exposed filter rendered as two separate blocks — a state change in ONE form's dependee incorrectly updates dependents in EVERY form that happens to share the same field name, not just the form that actually changed.

Steps to reproduce

This mirrors a real Views exposed numeric/date filter, whose `#states` selector is built from the filter's configured operator_id (see FilterPluginBase, Date, BooleanOperator) — identical across every rendered instance of the same View/filter on a page. No Views needed to reproduce it though; plain markup + core's #states behavior is enough:

1. Render two independent <form> s on one page. Give each an identical radio group named created_op ("Is equal to" / "Is between", default "Is between"), plus dependent fields using #states:
- value, visible when :input[name="created_op"] = "value"
- min and max, visible when :input[name="created_op"] = "between"
2. Load the page. Both forms correctly show Min/Max (Value hidden), matching their shared default.
3. Click Form A's "Is equal to" radio only. Leave Form B untouched.

Actual behavior: Form B also switches to showing "Value" (hiding Min/Max), even though its own radio still shows "Is between" selected.

Expected behavior: only Form A should update. Form B should be entirely unaffected by a change in a different form's fields.

Proposed resolution

When a dependent needs to watch a dependee selector that matches more than one element, it should first check whether any of those matches live in the same `
` as the dependent itself. If so, it should only listen to (and only react to) that subset — not every matching element on the page, as it does today.

If there's no such match — the dependent isn't inside a <form> at all, or none of the matches share one with it — nothing changes: it falls back to today's exact behavior, matching the whole page. This keeps existing, correct uses of `#states` (including ones that intentionally span forms, or that live outside a <form>) working exactly as before; it only narrows things down in the specific case that's currently broken.

Remaining tasks

- This scopes to the nearest <form> ancestor specifically. `#states` is also used outside a <form> context (e.g. some off-canvas/dialog widgets) — the fallback preserves existing behavior there, but a more general "nearest shared scope" concept (not tied specifically to <form>) may be worth discussing if this pattern shows up outside forms too.
- Automated test coverage: core's existing states.js coverage (JavascriptStatesTest.php / form_test`'s JavascriptStatesForm.php, and the Nightwatch statesTest.js) all exercise a single form on the page. Landing a fix here should add a test rendering the same states config twice with a duplicate field name, covering both the leakage case and the two fallback branches above (no <form> ancestor; genuine same-form radio/checkbox groups, which must keep working exactly as before).

Issue fork drupal-3622218

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

sasanikolic created an issue. See original summary.

sasanikolic’s picture

Issue summary: View changes
sasanikolic’s picture

Issue summary: View changes