Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-cascade-6] Should the scope proximity calculation be impacted by nesting scopes? #10795

Open
mirisuzanne opened this issue Aug 28, 2024 · 12 comments

Comments

@mirisuzanne
Copy link
Contributor

mirisuzanne commented Aug 28, 2024

Background:

The published definition of 'scope proximity' states that:

If two declarations both have elements selected by scoped descendant relationships applying weak scoping proximity, then the declaration with the fewest generational hops between the ancestor/descendant element pair wins.

If multiple such pairs are represented, their weak scoping proximity weights are compared from innermost scoping relationship to outermost scoping relationship (with any missing pairs weighted as infinity).

However, in the Editor's Draft, the second paragraph was removed and the first paragraph adjusted, so that each scoped selector has one single scope root and a single proximity number.

In our publishing discussion last week, @mdubet asked to reconsider this.

How it might work:

In order to find a 'proximity', we need both a 'subject' element and a ':scope' element. Then we count the 'steps' between one and the other.

Nested @scope rules are allowed. Each scope rule's <scope-start> selector is 'scoped' by the parent scope rule. If we want scopes to accumulate with nesting, we have to determine which subjects we are comparing to which roots. Given this example:

@scope (a) {
  @scope (b) { 
    c { /* … */ }
  }
}

I see two options (though I believe they might be functionally the same??). The scope proximity weight for c is one of:

  • [c-to-b-distance, c-to-a-distance]
  • [c-to-b-distance, b-to-a-distance]

In either case, I believe the proposal is to compare proximities from inner-most to outer-most.

But why?

I think this would be a reasonable approach. At least, it makes some sense to me that things might work this way. But I can't think of an actual use-case where I would rely on this behavior. I'm not opposed, but I'm also not sure how useful or complex it is.

@mirisuzanne
Copy link
Contributor Author

Thinking through it a bit more, and discussing with @argyleink, I don't really have any reason not to do this. The alternative is falling back on source order, which isn't better than multi-step proximity.

And I believe there's no difference between the two approaches above. The distance between two roots will always be equivalent to the additional distance between a subject and ancestor root. So the approach seems straight-forward.

So unless there's push back from other implementors (@andruud made the initial change here?), I'm going to propose we resolve on @mdubet's proposal here. Marking this as agenda+ to try and get that resolution.

@andruud
Copy link
Member

andruud commented Aug 30, 2024

@mirisuzanne In other words, this would introduce a dynamic number of cascade criteria (for the first time)? A bit like specificity, but instead of (A,B,C), it's a variable number of components.

But why? [...]

Last time this came up, we concluded that: 1) it adds complexity (both for impl and authors' mental model), and 2) it's not useful. Your answer to this question suggests that nothing has changed. Therefore, I do oppose this change, as it seems to (at best) only be about theoretical purity at the expense of other things.

I'm also not sure how [...] complex it is

We'd ideally investigate that a little bit before making any moves spec wise. @scope also shipped a long time ago in Blink. I would need to be able to prove that we even can ship such a change without breakage. Otherwise, we might end up with subtly different cascade behaviors forever, which is worse than just aligning on the current spec.

I'm going to propose we resolve on @mdubet's proposal here

We should minimally first answer the "But why?" with an actual answer, and explain why the more complex behavior is useful after all.

@emilio
Copy link
Collaborator

emilio commented Oct 8, 2024

cc @dshin-moz

@dshin-moz
Copy link

So given something like

<div class="scope-2">
  <div><div><div>
    <div class="scope-1">
      <div class="styled"></div>
    </div>
  </div></div></div>
</div>

and given below rules:

@scope (.scope-2) {
  @scope(.scope-1) {
    .styled {
      background: blue;
    }
  }
}

/* Outer scope proximity, as per proposal, is infinity */
@scope (.scope-1) {
  .styled {
    background: green;
  }
}

The concern is that the applied .styled would depend purely on the order of declaration, right?

FWIW, authors that want this could coax this out by using & and relying on specificity, becoming very CSS Nesting-like:

@scope (.scope-2) {
  @scope(& .scope-1) {
    & .styled {
      background: blue;
    }
  }
}

@scope (.scope-1) {
  & .styled {
    background: green;
  }
}

@dshin-moz
Copy link

As for adding a dynamically-sized cascade criteria... I generally agree with @andruud - Concerned about complexity on implementations/authors. Could adding a count of nested @scope work as an approximation that does not require dynamic sizing?

@mirisuzanne
Copy link
Contributor Author

To flesh that proposal out a bit, we'd have a (consistently) two-part value, including:

  • proximity steps to the nearest root
  • count of active scopes

In your example, the first rule has a scope of [1,2] and the second has a scope of [1,1]. As with specificity, we would compare those one at a time - moving to the scope-count as a tie-breaker only when the proximity is equal.

I would be happy with that as an approximation.

@andruud
Copy link
Member

andruud commented Oct 10, 2024

(I said elsewhere that I'd look into the complexity and performance issues re. adding a dynamic number of cascade criteria, but I'm still working on that, so I won't comment on that yet.)

Could adding a count of nested @scope work as an approximation that does not require dynamic sizing?

I would be happy with that as an approximation.

That would be much more acceptable, so +1 from me.

I would also argue that it's better for authors to not over-complicate the cascade criteria even more, and outer scopes just adding a flat 1 to a single tie-breaker sounds like an easier model to manage mentally.

@mirisuzanne Once, you also believed in the benefit of keeping it simple here:

"In my mind proximity is a useful heuristic in the simple cases - and this logic [single proximity] continues to handle those cases well. Once things get more complicated, authors will likely need to think about other cascade controls: layers, specificity, etc. With a heuristic like this, I think it would be a mistake to get too clever about solving more complex scenarios in an abstract or magical way."
[1]

"I don't see any reason to have a specificity-like cascade mechanic based on 'how many scopes were used to get here'. That would over-complicate what scope is about."
[2]

I still haven't seen an actual reason to change anything here, but I can live with @dshin-moz' proposal in any case.

@mirisuzanne
Copy link
Contributor Author

I do still think it's worth keeping this simple. I'm just happy to have the conversation - and want to make sure we're getting the right balance. Simple for authors to reason about is more important to me than simple for browsers to track. And I'm curious what makes the most sense to others.

@emilio
Copy link
Collaborator

emilio commented Oct 11, 2024

+1 to cascade order being already complicated enough fwiw. I actually wonder if scope proximity is all that useful to begin with..

@andruud
Copy link
Member

andruud commented Oct 15, 2024

I've prototyped the original proposal in Blink, and there will be performance regressions if we do this. Not as severe as I feared, but still enough that I think we should strongly consider @dshin-moz' proposal instead. That behavior is also easier for authors to comprehend IMO.

Otherwise, we could actually consider removing proximity entirely (as @emilio is hinting at). I kind of regret not making @scope just about scoping.

@romainmenke
Copy link
Member

Could adding a count of nested @scope work as an approximation that does not require dynamic sizing?

That also seems good to me.


I actually wonder if scope proximity is all that useful to begin with..

Otherwise, we could actually consider removing proximity entirely (as @emilio is hinting at). I kind of regret not making @scope just about scoping.

Proximity is actually the CSS feature we are most excited about.

It is very common for us to have components and layouts with areas that can contain other components. With both potentially having content from a wysiwyg editor. Many CMS's are moving towards very flexible page builders where content editors can nest components in various ways.

The only way to style these as designers would expect them to appear is by using @scope and if @scope has proximity.

An abstracted example of what we often encounter:

(I bet that component authors encounter similar issues but at a more granular/smaller level?)

I really hope that we don't lose proximity.

@mirisuzanne
Copy link
Contributor Author

mirisuzanne commented Oct 16, 2024

Yeah, I understand concerns about proximity being heuristic and associated with scope, but:

  • Proximity is extremely useful for situations like Romain mentioned. Enforcing explicit lower boundaries everywhere requires careful coordination across entire projects, which is not always possible. Implicit tools are helpful!
  • It's not a huge survey, but my quick mastodon poll isn't even close. This is something authors have been asking for since 2009 at least. Nicole Sullivan used to give talks about it.
  • We could look for a way to trigger it explicitly, but it requires the same parts as other scoping, and is used for the same semantic purpose (components negotiating ownership). So if we did split out the syntax, it would need to part of or at least based on scoping.

But I haven't actually seen any use-cases where you want one of these behaviors and don't want the other. I've only seen hand-wringing about it, and no examples of why it's not useful or should be separate.

I'm happy to have that conversation here or elsewhere. I also don't want to ship something if we don't think it will work. But I'm not sure how to respond when the concerns are never fleshed out beyond vague unease.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: TPAC/FTF agenda items
Status: No status
Development

No branches or pull requests

6 participants