w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.36k stars 641 forks source link

[css-values-4][Editorial] `<condition>` type that other specs reference #10457

Open LeaVerou opened 3 weeks ago

LeaVerou commented 3 weeks ago

This came up when I went to implement the resolution from #10064 and spec my if() proposal.

Currently, we have several specs using one or more types of conditionals, and in every case, this is defined inline. This means the boolean operators (and, or, not) are also defined inline, which is awkward, error-prone, and easy to forget.

Some examples:

Media Queries 5:

<media-query> = <media-condition>
             | [ not | only ]? <media-type> [ and <media-condition-without-or> ]?
<media-type> = <ident>

<media-condition> = <media-not> | <media-in-parens> [ <media-and>* | <media-or>* ]
<media-condition-without-or> = <media-not> | <media-in-parens> <media-and>*
<media-not> = not <media-in-parens>
<media-and> = and <media-in-parens>
<media-or> = or <media-in-parens>
<media-in-parens> = ( <media-condition> ) | <media-feature> | <general-enclosed>

@supports:

<supports-condition> = not <supports-in-parens>
                     | <supports-in-parens> [ and <supports-in-parens> ]*
                     | <supports-in-parens> [ or <supports-in-parens> ]*
<supports-in-parens> = ( <supports-condition> ) | <supports-feature> | <general-enclosed>
<supports-feature> = <supports-decl>
<supports-decl> = ( <declaration> )

CSS Conditional 4 adds…:

<supports-feature> = <supports-selector-fn> | <supports-decl>
<supports-selector-fn> = selector( <complex-selector> )

[CSS Conditional 5 adds…](https://drafts.csswg.org/css-conditional/#at-supports-ext

<supports-feature> = <supports-selector-fn> | <supports-font-tech-fn>
                    | <supports-font-format-fn> | <supports-decl>
<supports-font-tech-fn> = font-tech( <font-tech> )
<supports-font-format-fn> = font-format( <font-format> )

@container:

<container-condition> = [ <container-name> ]? <container-query>
<container-name> = <custom-ident>
<container-query>     = not <query-in-parens>
                      | <query-in-parens> [ [ and <query-in-parens> ]* | [ or <query-in-parens> ]* ]
<query-in-parens>     = ( <container-query> )
                      | ( <size-feature> )
                      | style( <style-query> )
                      | <general-enclosed>

<style-query>         = not <style-in-parens>
                      | <style-in-parens> [ [ and <style-in-parens> ]* | [ or <style-in-parens> ]* ]
                      | <style-feature>
<style-in-parens>     = ( <style-query> )
                      | ( <style-feature> )
                      | <general-enclosed>

@import:

<import-conditions> = [ supports( [ <supports-condition> | <declaration> ] ) ]?
                     <media-query-list>?

Tab’s @when proposal:

media() = media( [ <mf-plain> | <mf-boolean> | <mf-range> ] )
supports() = supports( <declaration> )

In fact, there is an inline issue right there proposing a lighter form of what I’m arguing for here:

Define "boolean algebra, with X as leaves" in a generic way in Conditional, so all the conditional rules can reference it directly, rather than having to redefine boolean algebra on their own.

Proposal

I propose we introduce a new <boolean> or <condition> value type that other specs can reference. It will include both the grammar for the boolean algebra, as well as bare and functional forms for each conditional. Each conditional type should also include metadata like:

Not all condition types need to be defined in values, we could extend the <condition> token in other specs, as we often do with value types. E.g. size queries may be a better fit in css-contain, since they are not valid anywhere else.

The prose should explain that specs using <condition> should specify:

  1. Which types of conditions are allowed?
  2. Which types of conditions can be specified bare, i.e. without a function name.

To make this more concrete, this is the state of the current specs with conditionals with this framing:

Host supports() media() size() style()
@container - - - ✅ Bare
@supports ✅ Bare - - -
@media - ✅ Bare - -
@import - ✅ Bare -
if() -
@if / @when -

While this change is editorial, I think it will pave the way for a lot of quick DX wins involving mixing and matching conditions (e.g. supports() in @media.

It could also allow creating JS APIs that handle conditions generically, avoiding the current issue of e.g. having matchMedia() for media queries, but no way to detect when a container query matches or not.

tabatkins commented 3 weeks ago

Committed a first pass at the generic <boolean> grammar

LeaVerou commented 3 weeks ago

Committed a first pass at the generic <boolean> grammar

Thanks! I think the ergonomics will become more clear once we edit specs to reference it too.

Btw if we call it <boolean> that sort of implies we also have constants like true and false. If we don’t want that, perhaps <condition> is more clear.