mikke89 / RmlUi

RmlUi - The HTML/CSS User Interface library evolved
https://mikke89.github.io/RmlUiDoc/
MIT License
2.57k stars 295 forks source link

add support for `:scope` as a pseudo-class for querySelector[All] / matches / closest #578

Open Paril opened 5 months ago

Paril commented 5 months ago

:scope is a super useful tool in modern web dev, and it's hard to live without it once you're used to using it. This PR adds support for :scope in its most useful context: used as a scope limiter in Element-based selector functions.

This PR only covers the code-side use of :scope; :scope in an RCSS file will do nothing. I don't believe it's useful at all in RCSS without support for @scope or :root, so I didn't bother with those.

All of the use-cases here should apply & work: https://developer.mozilla.org/en-US/docs/Web/CSS/:scope#using_scope_in_javascript

mikke89 commented 5 months ago

Hey, and thanks a lot for the contribution.

I haven't really looked at this pseudo class selector before. Looks handy when using query selectors, especially a bit complex ones.

I am a bit worried about how invasive this is code-wise. That's not great for maintenance and refactoring. I wonder if there is some way we could improve that? I understand that this selector can only be used once in a query, maybe something we could take advantage of, also performance-wise.

How does this selector behave when used in descendant elements? We should have some test cases for that.

What happens when using the selector in RCSS?

Paril commented 5 months ago

It's a staple of modern web development; its main use is to be able to use > while referring to the element being queried on, essentially a faster way of doing Array.from(element.childNodes).filter(e => e.matches("xxx")) or any other previously-necessary hack, one can just do element.querySelectorAll(":scope > xxx").

This was the least invasive way I had found to implement it; it only requires tracking a scope down to the querying function. I'm not sure where else it could go, and I didn't really want to modify all of the types involved just to store a single field on them. My first iteration tried to shove an element reference in structural_selectors somewhere but that didn't end up working. If you have a suggestion, let me know and I'll hammer away at it.

We don't have :is, but since it's basically just inverted :not, I did see that this works as expected: :scope > p:not(:scope > div) - it's a dumb selector but it behaves the same as it does on Chrome. When it comes to combining with ~ and +, that appears to work as expected as well; these two matches test succeed, as they do on Chrome:

    { "G", "#F ~ :scope",    true },
    { "G", "#F + :scope",    true }

I've never seen that used in the wild though, heh. The selector can appear more than once in a query, but it always refers to the same element.

When used in RCSS, :scope is null and will never match anything, so the selector matches nothing. The only deviation from CSS here is that, in CSS, when :scope is null, it instead refers to :root - we don't have :root so I left that unimplemented, but in theory it would just need to refer to the <rml> element.

Paril commented 4 months ago

Converting to draft for now as I won't have time for a while to poke these.