Closed clarumedia closed 4 months ago
@clarumedia this is a solid thought; however, this isn't specified as it relates to the base styling for <select>
since that is left to the UAs and they're technically different elements. So while they often appear as one set of elements the options are actually not what's being rendered but what is being reflected. This is however one good thing of the new <selectmenu>
that we're working on as you can now achieve this and also see how this anatomy exists in this pen: https://codepen.io/gregwhitworth/pen/XWEBLwe
Now you could argue that you'd like <selectmenu>
to do this to appear as though it's truly reflecting the content within the option and that's worth considering although I can think of arguments for not doing this by default and leaving it for the author to do. Interested to get your thoughts as well as @dandclark. This is a usecase worth thinking about because options can now have arbitrary content/styles and I'm curious if common examples clone and reflect all content or just the value.
@gregwhitworth One thought is a CSS directive or attribute for the selectmenu tag that would tell it to incorporate the CSS of the selected child. Sort of reverse inheritance from the selected item. I can see this could potentially get messy if the child does things like expand horizontally or vertically (for example). Perhaps if we could specify which CSS attributes could effectively be "inherited" from the selected child back to the parent ? Something like:
select { color: inherit-selected; }
The Open UI Community Group just discussed [select] Inconsistent CSS handling when the "selected" attribute has been set on an option ?
.
The Open UI Community Group just discussed [select] Inconsistent CSS handling when the "selected" attribute has been set on an option ? #571
.
Font selection may also be relevant here. This is from the Font Access API demo.
The Open UI Community Group just discussed Should the inner HTML & styles of the selected option be copied into selected-value?
.
I believe this is a very related feature request.
https://github.com/whatwg/html/issues/6507
And I have created a related proposal for this here:
I think it could be worth considering whether <option>
elements need a prop to specify a string-only representation of the contents when selected, for a couple reasons:
<selectmenu>
, inputs do not support complex content, and I see benefit in having consistency across select-only vs. editable versions of selection componentsThe Open UI Community Group just discussed [select] Should the inner HTML & styles of the selected option be copied into selected-value?
.
After the discussion last week, we arrived at the following solutions:
I'm personally in favor of 1 as the author will have the context of what they do and don't want to show and it's the easiest from a DX perspective. One of the examples raised was country select menus which have flag images inside of the option; you'd want this flag represented as well in the selected value. I shared a few other examples, such as email applications that showed an avatar, name and title but upon selection just the name was copied into the selected value.
My thoughts on these options:
Cloning the innerHTML: Copying the HTML of the option seems like a reasonable choice. I would prefer a deep clone of the content over actually using innerHTML
, since skipping the parser should be less complex and more performant.
There are pitfalls here though:
id
references: if any script on the page refers to the contents of an option via ID, that will break.<option>
itself included in the content that's copied, or only its children? If only the children are copied, then styles like option.redOption { background-color: red }
will break. Devs will need to be careful to target styles to the children, and not the option elements themselves. But copying the option itself seems suspicious...then we'd have an <option>
in a <button>
which seems weird.Implicit slotting: Using implicit slotting would require the slotted option to appear in two places at once when the <selectmenu>
's listbox is open. This is not supported by imperative slotting today, and I think it would add too much complexity. We can build on https://github.com/openui/open-ui/issues/616 to solve this long-term, but I don't want to have the initial version of <selectmenu>
take a hard dependency on that.
Provide an attribute: I'm open to this. If we go this route we could probably punt the attribute to a future version and use a simple default like the current behavior of copying innerText only for <selectmenu>
"V1".
Keep it as currently defined (inner text): This is my preferred choice if and only if the pitfalls listed with the Cloning innerHTML choice are too concerning/difficult to work around. We could keep this as the default and add an opt-in attribute later for more complex behavior.
Forgive me for not following along better, but are these cloning options cloning things only within the lightdom, or are they cloning things from the lightdom into the shadowdom?
By default, innerHTML
and Node.cloneNode()
will only get lightdom content, but we may want to define things for <selectmenu>
so that shadowdom content gets copied as well.
The Open UI Community Group just discussed [select] Should the inner HTML & styles of the selected option be copied into selected-value? #571
.
I believe it should contain everything the option has inside of it. If the developer does not want to show the image or other SVG inside of the selected-value, this could be easily achieved by targeting and hiding it with CSS. This at least would be easier than the other way around.
What I do wonder about is the use of id's inside of the option html... but the question about this becomes about what do people want most: automatically clone images, or not use id's inside of option html. It's a hard one.
Maybe it could be an attribute on the select menu?
Something like allow="text"
and default set it to all
?
Or maybe that is a bridge too far? Very interesting topic.
The demo where I wanted to use it can be found here (had to fix it with JS, disable JS to see result without it): https://codepen.io/utilitybend/pen/zYLoqge
I believe that we should keep selectmenu as is and not copy/clone/slot anything from the selected option into the selected value slot. Copying innerHTML seems error prone and could lead to funny results especially if we are always doing it by default.
Copying innerHTML and styles could be recreated quite easily if that's what the author wants to do with a little bit of script: https://jsfiddle.net/jarhar/83gpscra/
selectmenu.addEventListener('change', () => {
selectedvalue.innerHTML = selectmenu.selectedOption.innerHTML;
selectedvalue.className = selectmenu.selectedOption.className;
});
We could try slotting the selected option into the selected value when the listbox is closed, but I don't think that having the selected value change when the listbox is opened vs closed is great. Using a mirroring primitive to do this would be ideal, but implementing and shipping something like that is a lot harder than the selectmenu element itself. If mirroring is ever implemented in the future, then we could provide an opt-in upgrade path to use it in selectmenu.
https://github.com/openui/open-ui/issues/435 also brought up the idea of being able to automatically apply something selectable in CSS to both the selected option and selected value, such as an attribute. I still think that doing this with script as I did in my example above is good enough and will make selectmenu easier to ship.
While I mostly agree when it comes to safety of this, there is a downside by doing this with JS. Just a few pointers that come to mind here:
The original state should also be taken into account. As the default selected
option might change on page load
(quick example that comes to mind: server selection for a game, when server is full on a new page load, another item should be default selected and previous option disabled).
If we were to switch the initial innerHTML
on page load, chances are high, there is a visual delay caused by this initial switch. I already seem to have this issue by just using 2 svgs.
Another thing to take into account, as it stands at the moment, the change event won't cause the selectmenu to update when using keyboard(arrow) navigation to toggle between the options. But this might be something else that can be handled later on.
How about an attribute which can be toggled to change the behaviour ? Something like:
inheritInnerHTMLAndCSSFromSelectedOption=true
Defaults to false for safety ?
as it stands at the moment, the change event won't cause the selectmenu to update when using keyboard(arrow) navigation to toggle between the options
If you're talking about changing the selected value while the listbox is closed, we have decided to not support this: https://github.com/openui/open-ui/issues/433
If you're talking about using arrow keys to focus to next/previous options in the listbox, we shouldn't be changing the actual selected value until the user presses enter, right?
If you want to change the selected value before the user actually commits to the option by pressing enter and causing a change event to be fired, I think that's not a good idea
If we were to switch the initial innerHTML on page load, chances are high, there is a visual delay caused by this initial switch. I already seem to have this issue by just using 2 svgs.
If you can't run script before the first render, then yeah I guess you'd have to make the initial HTML include the correct HTML for the selected option. It would take more work but it isn't impossible.
How about an attribute which can be toggled to change the behaviour
Having an opt in like this does sound a lot better than doing it by default, but I'm worried that this isn't a good enough solution to make it past a whatwg/HTML review - namely that copying innerHTML in general might not be a good idea to allow at all, and that I don't think any other HTML elements have attributes to opt into behavior like this.
I think it's an interesting one to further discuss in a meeting. I'm really wondering what other people think about this. As there are so many use-cases where images/svg can be handy to be handled automatically and would remove the use of extra JS.
Wether it is with an extra attribute or not, really isn't my biggest concern to be honest. It's more about having the ability in general without relying on JS. People use a lot of libraries for these things already.
I understand accessibility concerns, or creation of multiple id's in the document, but should this be our concern or the developers concern when creating something? (Speaking as a full-time front-ender myself).
The Open UI Community Group just discussed [select] Should the inner HTML & styles of the selected option be copied into selected-value?
, and agreed to the following:
RESOLVED: Don't implement behavior to copy innerHTML from the selected option into the selected value for V1 of selectmenu
I talked to @mfreed7 and @domenic about what our options are to solve this, and this is what we came up with.
TL;DR I think that we should have the browser cloneNode() the contents of the selected <option>
into the <selectlist>
's <selectedoption>
element, replacing the child nodes of the <selectedoption>
element. If someone along the way to getting this standardized comes up with a more elegant solution, I'm happy to switch to it. I am currently implementing a prototype of this in chromium.
<option>
’s children into a ShadowRoot of <selectedoption>
<selectedoption>
would have a user-agent ShadowRoot, and every time that the selected <option>
changes, we would remove all children of the <selectedoption>
’s ShadowRoot, cloneNode the <option>
and append each child of the clone to the <selectedoption>
’s ShadowRoot.
We would also need to provide a pseudo-element, like ::selectedoption(), so that CSS can make distinctions between the <selectedoption>
and <option>
content and also target the content inside the <selectedoption>
’s ShadowRoot.
This is similar to how svg use works.
Issues:
<selectedoption>
or the <selectlist>
.document.querySelector(::selectedoption)
would give script access to nodes inside of the user-agent shadowroot. From there they can traverse up the tree.<script>
element? (You could use currentScript to access an inside-the-tree node.) We could try to avoid cloning scripts, but what about iframes? (they could use frameElement).<option>
’s children into the light DOM of <selectedoption>
This is the same as the cloning into ShadowRoot option, but instead of having a ShadowRoot to clone into, we would just replace the <selectedoption>
’s regular DOM children with the cloned nodes.
Issues:
<selectedoption>
though.element()
in CSS to mirror the content internallyFirefox implements this as a way to mirror content in CSS:
https://developer.mozilla.org/en-US/docs/Web/CSS/element
We could implement this and then use it internally to mirror the rendered output of the selected <option>
into the <selectedoption>
.
Issues:
<selectedoption>
separately from the <option>
.<selectlist>
element. So the mirrored image would need to be cropped or stretched to fit.<selectedoption>
, so no systems would actually know what’s inside the <selectedoption>
. Screen readers can’t read this, or at least would need to be modified in order to deal with it. What if you want to support selection?We could try to find a way to have the children of the selected <option>
also appear in the flat tree or layout tree of <selectedoption>
.
A similar idea was proposed here, although I’m not sure this would actually be the same thing: https://github.com/whatwg/html/issues/6507
Issues:
<selectedoption>
and <option>
. How does this work with CSS selectors? E.g. selectedoption > .foo { ... }
and option > .foo { ... }
are supposed to give different behaviors to accomplish the differential styling goal, but they are targeting "the same node", because CSS selectors select nodes, not flat tree/layout tree objects.This is the script that would be required for web developers to implement the “cloning into light DOM” idea:
selectlist.addEventListener('change', () => {
while (selectedoption.firstChild) {
selectedoption.firstChild.remove();
}
for (const newChild of selectmenu.selectedOption.cloneNode(true)) {
selectedoption.appendChild(newChild);
}
selectedoption.className = selectmenu.selectedOption.className;
});
Issues:
Thanks for the great summary Joey. I'll reiterate my perspective that, although this sort of light-DOM modification is strange and unprecedented, it seems to me to be the only reasonable option for accomplishing the use case.
I also want to note that this new technique, of elements that modify their own light DOM, might be necessary to solve a few other long-standing HTML feature requests. Namely:
Client-side includes (https://github.com/whatwg/html/issues/2791): what people generally want here, is something like <include src="foo.html"></include>
, where the contents of foo.html
end up inside the light DOM of the <include>
element.
Various "format my contents" element proposals, e.g. https://github.com/whatwg/html/issues/9294 or https://github.com/whatwg/html/issues/2404. Authors want something like <relativetime>2023-08-28T00:00:00Z</relativetime>
or maybe <relativetime value="2023-08-28T00:00:00Z"></relativetime>
to "display as" 2 days ago
. How would you do that? As noted in https://github.com/whatwg/html/issues/2404#issuecomment-1670907485 it's rare for the user agent to introduce new display text separate from the light DOM. If we just had those elements update their own light DOM, maybe it would work.
I'm not saying that we should necessarily tackle either of these two feature requests, or that the best way to do them is with light DOM modification. But I wanted to provide some evidence that the platform has been straining against this constraint of "light DOM is entirely for author territory, and should not be modified by UA script" for a while, and that if we consciously cross that boundary, in a well-designed way, it might open up new options going forward.
There hasn't been any discussion on this issue for a while, so we're marking it as stale. If you choose to kick off the discussion again, we'll remove the 'stale' label.
The plan is still to use clonenode to copy the contents of the selected <option>
into <selectedoption>
The Open UI Community Group just discussed [select] Should the inner HTML & styles of the selected option be copied into selected-value?
, and agreed to the following:
RESOLVED: we already resolved to clone content from options into `<selectedoption>`. We will stick with that resolution.
Chrome 103.0.5060.114
When an option is "selected" using the "selected" attribute, shouldn't the style of that option supersede the style of the select ? Use case: colours set on select options via CSS, the selected colour would display having been selected.