vaadin / web-components

A set of high-quality standards based web components for enterprise web applications. Part of Vaadin 20+
https://vaadin.com/docs/latest/components
449 stars 83 forks source link

Support standalone radio buttons #7840

Open Legioth opened 4 days ago

Legioth commented 4 days ago

Describe your motivation

There are cases where a more complex UI design requires that individual radio buttons are not siblings in the DOM. A simple case is if each of them needs to be wrapped in an additional <div> for styling purposes.

<vaadin-radio-group>
  <div class="style-me">
    <vaadin-radio-button label="A" />
  </div>
  <div class="style-me">
    <vaadin-radio-button label="B" />
  </div>
</vaadin-radio-group>

The problem is that a <vaadin-radio-button> remains rendered as checked when another one from the same group gets selected. If I manually give each one the same name, then the internal <input> elements get unchecked but there's nothing that removes the checked attribute from the parent component which means that it's still visually checked.

Describe the solution you'd like

One alternative would be to provide the same flexibility as with <input type="radio"> so that you could put radio buttons anywhere in the DOM as long as they use the same name value.

An alternative would be if <vaadin-radio-group> could detect <vaadin-radio-button> (or <input type="radio">?) elements that are descendants even if they are not direct children of the group. The benefit of this is that you don't need to manually define a name.

Describe alternatives you've considered

No response

Additional context

No response

web-padawan commented 4 days ago

If I manually give each one the same name, then the internal elements get unchecked but there's nothing that removes the checked attribute from the parent component which means that it's still visually checked.

This could be implemented by adding logic similar to SingleSelectionController from Material web components.

An alternative would be if <vaadin-radio-group> could detect <vaadin-radio-button> (or <input type="radio">?) elements that are descendants even if they are not direct children of the group.

This could be implemented by changing this line to query assigned nodes instead of this.children.

https://github.com/vaadin/web-components/blob/4991d83434a4cf546bdd540292a94ebffdb2228a/packages/radio-group/src/vaadin-radio-group-mixin.js#L91-L93

Legioth commented 4 days ago

Two wild ideas:

web-padawan commented 4 days ago

Could a MutationObserver somehow detect when the other button is no longer checked?

Probably not. There are no attributes that would change on the <input type="radio"> when toggling its checked state.

Legioth commented 4 days ago

I was thinking of weird effects like an internal childList change or something like that.

joelpop commented 4 days ago

I suppose there could be some weird (bad UX?) scenarios where selection of one (or more) of the radio buttons would then enable a sub-selection of a nested radio button group:

<vaadin-radio-group>
  <div class="style-me">
    <vaadin-radio-button label="A" />
  </div>
  <div class="style-me">
    <vaadin-radio-button label="B" />
    <vaadin-radio-group>
      <div class="style-me">
    <vaadin-radio-button label="B-1" />
      </div>
      <div class="style-me">
    <vaadin-radio-button label="B-2" />
      </div>
      <div class="style-me">
    <vaadin-radio-button label="B-3" />
      </div>
    </vaadin-radio-group>
  </div>
  <div class="style-me">
    <vaadin-radio-button label="C" />
  </div>
</vaadin-radio-group>

but in that situation, the unchecking of the radio buttons in the nested radio button group should be distinct from those of the outer radio button group.

Legioth commented 1 day ago

Any recursive finding of all children would have to stop searching if encountering another <vaadin-radio-group> element.

This actually makes me think of another way of implementing this. <vaadin-radio-button> could look through its own ancestors for a <vaadin-radio-group> in connectedCallback() and automatically register itself with the first one that it finds.