openui / open-ui

Maintain an open standard for UI and promote its adherence and adoption.
https://open-ui.org
Other
3.48k stars 186 forks source link

[select] Transforms applied to a `<selectmenu>` are not applied to the listbox #779

Closed xiaochengh closed 2 weeks ago

xiaochengh commented 1 year ago

(Originally filed as a Chrome bug)

If we add transform to a <selectmenu>, eg selectmenu { transform: scale(1.5) }, we probably expect every part of the selectmenu to be scaled. However, the listbox is not scaled. (See test case)

This is because:

  1. transform is a non-inherited property, so the listbox's own transform value is still initial
  2. The listbox is in the top-layer, which means it's not a descendant of the <selectmenu> in the box tree

As a result, the listbox doesn't get any transform.

I don't see any easy solution. Adding UA style to the listbox (eg selectmenu::part(listbox) { transform: inherit }) doesn't fix it.

gregwhitworth commented 1 year ago

Ahhh goodness, @xiaochengh I think you're correct that this would be the expected result. However, I agree with @josepharhar in the chrome bug that there are ways around this. As a dev, I'd probably reach for custom props or SASS mixins. However, these are not ideal but I don't see a solution to this that isn't more invasive. I know that @dbaron has a lot of historical knowledge on transforms so he may have some ideas.

Key thing that I think Open UI can do here is to understand how often this usecase will be hit by developers (eg: @jh3y has produced numerous demos with gorgeous animations and hasn't surfaced this issue). If we feel the issue is large enough we'll need to surface our recommendation to the CSSWG since they own this.

jh3y commented 1 year ago

This would be the expected behavior as anchor positioning ignores CSS transforms. The only way around this would be to apply the same transforms to the list box (assuming they were translations).

Unfortunately, when it comes to say scaling up the button on :active/:hover, the developer would need to counter for that then compensate in the anchor position and apply any scaling to the list box. I don't think it's a use case that warrants changing how these APIs work. It's mainly something for people to be aware of. Workaround would be to use calc with the anchor function to compensate for any scaling. For example, if the button was scaled. You could then set top with something along these lines anchor(bottom) + ((anchor(bottom) - anchor(top)) * var(--scaling)) (Or using anchor-size).

dbaron commented 1 year ago

I could imagine adding a mechanism to anchor positioning that allowed applying the transform taken from an anchor to the anchor-positioned element. I could imagine a mechanism to do this working roughly as follows (although there are many options here that all have different behavior):

  1. a transform is established in some way by an anchor element
  2. add a new property to apply a transform from an anchor element to an anchored element
  3. in the steps to compute the CTM, add a new set of steps (probably prior to the current step 2, although it could also be at the end) that applies a transform computed roughly like the accumulated 3d transform matrix but accumulated (a) from the nearest common ancestor of the anchor element and the anchored element (b) to the anchor element.

I think we perhaps don't want step 3 above to reverse-apply and the reapply the transform between the common ancestor and the anchored element, though I'm not sure of this.

But I'm not particularly sure of this math; it probably requires testing some options and seeing if they do reasonable things, and seeing whether the above math gets the transform origin behavior correct.

mfreed7 commented 1 year ago

I could imagine adding a mechanism to anchor positioning that allowed applying the transform taken from an anchor to the anchor-positioned element. I could imagine a mechanism to do this working roughly as follows (although there are many options here that all have different behavior):

@xiaochengh and @tabatkins what do you think of this proposal? For the <selectmenu> use case, there are several things that would appear broken:

tabatkins commented 1 year ago

Hm, I'm not confident that this would actually work in the common case without thinking more about it; translates are fine, but scales and rotates are completely dependent on the transform origin, not to mention skews!

I worry a lot about this one, since transform:translate is a common centering solution.

It's only common because centering didn't previously exist in a reliable form. It now does, in the form of the alignment properties (justify/align-*). We shouldn't design for the assumption of people using legacy hacks; we should only do something with this if there's non-hacky use-cases we want to cover.

mfreed7 commented 1 year ago

Hm, I'm not confident that this would actually work in the common case without thinking more about it; translates are fine, but scales and rotates are completely dependent on the transform origin, not to mention skews!

Totally agree that the main thing to worry about is translates.

I worry a lot about this one, since transform:translate is a common centering solution.

It's only common because centering didn't previously exist in a reliable form. It now does, in the form of the alignment properties (justify/align-*). We shouldn't design for the assumption of people using legacy hacks; we should only do something with this if there's non-hacky use-cases we want to cover.

While I do agree this is a "legacy hack", I believe it's still quite commonly used. The use case I'm afraid of is a legacy site (using transform:translate like this) that wants to upgrade from <select> to <selectlist> - things will break. Similarly, a site using transform:translate and a 3rd party component library, and the library starts using <selectlist>.

If it isn't possible to make this use case work, or making it work sacrifices something from the regular feature, then I agree we shouldn't support it. But if it's possible to support, I think we should try.

dbaron commented 1 year ago

I think there likely is a reasonable way to get a reasonable set of the cases of not-just-translation transforms right, but it probably requires prototyping in JS to check the math and check which option does the right thing. (That is, it requires a good bit more work than my current opining in comments!)

I think perhaps the less clear question if we add a property to apply the transform from an anchor, is what the expected interaction is with transforms that are on elements between the anchored element and least-common-ancestor(anchored element, anchor element). I'm not sure there's a good answer to that, since I suspect there may be both use cases that would expect them to be ignored (or cancelled out) and use cases that would expect them to be honored (but compounded how with the transforms from the anchor element?) -- though maybe a good enough answer to that question is to specify some interaction that does reasonable things if there's only a transform in one of the two places -- and if you have transforms in both then you probably get something weird.

xiaochengh commented 1 year ago

+1 to doing some JS prototyping first.

I think perhaps the less clear question if we add a property to apply the transform from an anchor, is what the expected interaction is with transforms that are on elements between the anchored element and least-common-ancestor(anchored element, anchor element).

We don't need to worry about this, because there can't be such elements.

The anchored element must be absolutely positioned, and its anchor must be in the containing block of the anchored element. Also, any transform will establish a containing block. As a result, the LCA is always the containing block, and there can't be any additional transforms between the LCA and the anchored element.

github-actions[bot] commented 6 months ago

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.

josepharhar commented 5 months ago

I could imagine adding a mechanism to anchor positioning that allowed applying the transform taken from an anchor to the anchor-positioned element.

In the sentiment of this comment, I agree that this seems like a general anchor positioning issue and not a selectmenu/selectlist/select issue. Not sure what to do about it though.

josepharhar commented 2 weeks ago

I think this should be solved by anchor positioning rather than being a select-specific behavior we add for appearance:base. I'll post about it in the crbug