Skip to content

fs-list: radio group with mixed operators leaves stale conditions (selecting one radio doesn't clear siblings using a different operator) #796

Description

@pgrzyb

Related Attributes

fs-list (@finsweet/attributes-list) — reproduced on 1.14.6 / current master.

Describe the bug

Filter conditions are keyed by field + operator (in src/filter/standard/conditions.ts, id = ${fieldKey}_${op}, and the matching findIndex in src/filter/standard/index.ts). When two radio inputs in the same native radio group target the same field but use different operators, selecting one radio does not clear the other's condition.

A native radio group is mutually exclusive only in the DOM: selecting radio B auto-deselects radio A without firing an input/change event on A, so the form listener never runs for A and A's condition is never cleared. Because A and B resolve to different condition ids (different operator), both conditions stay "live" simultaneously.

With the default conditionsmatch="and", the two leftover conditions get ANDed. If they're semantically exclusive (e.g. == 0 and >= 1), the combination is impossible → 0 results, silently. (Setting conditionsmatch="or" doesn't help — it then matches everything.)

It works fine when every radio in the group shares the same operator, because they all collapse into a single field+op condition that gets overwritten on each selection. The bug only appears once operators differ within one group — which makes it confusing to diagnose, since "the default operator works."

To Reproduce

Steps to reproduce the behavior:

  1. Build a standard fs-list with a numeric field qty. Items expose it as a number, e.g. <div fs-list-field="qty" fs-list-fieldtype="number">0</div>. Have some items with qty = 0 and some with qty >= 1.

  2. Add a filters form with one radio group (same name), all fs-list-field="qty":

    <!-- "All" -->
    <input type="radio" name="stock" fs-list-field="qty" fs-list-value="">
    <!-- "Out of stock" — default operator (equal) -->
    <input type="radio" name="stock" fs-list-field="qty" fs-list-value="0">
    <!-- "In stock" — custom operator -->
    <input type="radio" name="stock" fs-list-field="qty" fs-list-value="1" fs-list-operator="greater-equal">
  3. Click "Out of stock" → condition qty equal "0" → list filters correctly.

  4. Click "In stock" → expected qty >= 1. The DOM deselects "Out of stock", but the engine now holds both qty equal "0" AND qty greater-equal "1".

  5. See error: with the default conditionsmatch="and", this is qty == 0 AND qty >= 10 results.

(Inspecting list.filters.value.groups[0].conditions after step 4 shows both conditions present, the deselected one still carrying its old value.)

Expected behavior

Selecting a radio in a group should supersede the other radios in that same group, regardless of operator — the conditions belonging to the now-deselected radios (same name / same field) should be cleared, so only the selected radio's condition stays active.

Screenshots

N/A.

Affected devices

Not device-specific — reproducible in any modern browser (verified in Chrome). Not a rendering/OS issue.

Links

No public reproduction link available, but the steps above are self-contained and reproduce it from a blank fs-list setup.

Additional context

  • Root cause appears to be the input handler in src/filter/standard/index.ts (it only replaces a same-field+op condition or pushes a new one — it never removes the condition of a deselected sibling radio), combined with the field+op keying in src/filter/standard/conditions.ts.
  • Possible fix: when an input belonging to a radio group changes, clear the conditions for the other radios in that same group (by name/field) before applying the selected radio's condition.
  • Possibly related to Checkbox filters are not updated when filter changes were made with API #762 (radio/checkbox condition ↔ DOM state syncing), though that issue is the inverse direction (API-driven changes not reflected in the DOM).
  • Footgun that can mask this: the operator value must be exactly one of the documented strings (e.g. greater-equal); invalid values are silently discarded and fall back to equal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions