w3c / csswg-drafts

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

[css-anchor-position-1] Alternative syntax for auto position fallback #9196

Closed xiaochengh closed 5 months ago

xiaochengh commented 11 months ago

The current spec uses auto and auto-same keywords in the anchor() function to create automatic position fallbacks, e.g., left: anchor(auto); right: auto.

I think this need to be reworked. Reasons:

Proposal

Remove everything about the auto and auto-same anchor side keywords, and:

Add a new position-fallback-auto property

position-fallback-auto: none | flip-block | flip-inline | flip-x | flip-y | flip-both | compass

A none value means no auto fallbacks are generated.

A flip-block value creates a fallback style where, intuitively, all the block-axis values are flipped. Specifically:

The other flip-* values for a single axis are similar.

The flip-both value creates 3 fallback styles: block axis flipped, inline axis flipped, then both axes flipped.

The compass value allows creating fallback positions around the anchor (e.g., top, left, bottom, right). Specifically:

Usage in @try block

The property can also be used in a @try block to create additional fallbacks based on a @try block style. This part is similar to the current spec and hence omitted.

Add new same and opposite anchor side keywords

The current auto and auto-same keywords are also useful for non-fallback purposes. For example, inset: anchor(auto-same) creates an inset-modified containing block that's the same as the anchor box.

To support this use case, we add same and opposite anchor side keywords. They are evaluated the same as the current auto-same and auto, but don't create any fallbacks.

Also, same and opposite are not swapped in flip-* fallbacks.

Minor discussion topics

css-meeting-bot commented 11 months ago

The CSS Working Group just discussed [css-anchor-position-1] Alternative syntax for auto position fallback, and agreed to the following:

The full IRC log of that discussion <dbaron> TabAtkins: xiaochengh was exploring space for position stuff. While thinking about use cases brought up for alternate proposal, realized the current stuff we have in the spec for auto fallback isn't sufficient. auto and auto-same keywords. Current behavior is that they resolve to appropriate side of anchor and auto-generate try blocks that flip to other side. Only thing changed in try blocks are side -- margins don't change as well.
<dbaron> TabAtkins: That seems less than useful.
<dbaron> TabAtkins: xiaochengh's alternate proposal is to separate the automatic try block generate to an explicit fallback property that has keywords that say how to generate the fallbacks, a bunch of keywords listed in proposal. (flip-both and compass try all 4 possibilities)
<dbaron> TabAtkins: This affects more than just the inset properties. Also affects other box model properties. So margin-bottom after a flip-y ends up as a margin-top.
<dbaron> TabAtkins: This seems substantiallly better to me.
<dbaron> TabAtkins: In the simplest cases it's identical, but in nontrivial cases it does much better behavior. I'm happy to acccept these.
<dbaron> TabAtkins: Other use for auto and auto-same keywords -- automatically resolving to the appropriate side -- can use in the inset shorthand and get all the sides specified. Can keep that behavior and rename the keywords to not imply auto-fallback.
<jensimmons> q+
<dbaron> TabAtkins: I think this opens up possibilities for more fallback. Could build some position-area stuff in here. Could specify position-area spots and it could do appropriate flipping as well. I think it has growth area to fill in remaining fallback cases.
<astearns> ack fantasai
<dbaron> fantasai: I think this is interesting. I find the auto keywords inside ?? to be confusing. I can never remember what the auto thing does. Can we use more descriptiive keywords?
<dbaron> TabAtkins: suggested keywords are same and opposite. So top: anchor(same) means top.
<TabAtkins> top: anchor(same)
<TabAtkins> means top: anchor(top)
<iank_> we can bikeshed with authors
<dbaron> fantasai: another idea would be 'inside' and 'outside'
<astearns> ack jensimmons
<fantasai> top: anchor(inside) is top: anchor(top) -- it anchors it inside the anchor box
<fantasai> top: anchor(outside) is top: anchor(bottom) -- it anchors it outside the anchor box
<dbaron> jensimmons: Comparing this idea to current spec, not much to say. But comparing to proposal we presented at f2f, the higher-level idea of fallbacks. In the position-area/inset-area model, where you say you want above, in both right and center columns... at the bottom do you want it right and center. What if you want above all the way across, falling back to just center if below. But maybe just flipping is what people usually want. No
<dbaron> opinion on whether a good iteration on current spec.
<nicole> Here is a doc of examples we worked through (you'll need to ask for access) https://docs.google.com/document/d/1Dsu91zGfhG-qBbZvwOz13SS_m5dTb_ZHbDbonUf1qnM/edit?usp=sharing&resourcekey=0-8b2dD7pNg1Ruovm0QlhNkA
<dbaron> TabAtkins: Three levels of fallback precision: (1) is simply mirror. (3) is arbitrary things in next fallback, can use try blocks in @position-fallback. (2) between those is to adjust as necessary, mostly just moving to different area. I think this syntax is extendable to take position-area keywords as well.
<xiaochengh> q+
<dbaron> TabAtkins: So you could start out as top center and move to bottom all and have it work out appropriately, with the necessary flips.
<dbaron> TabAtkins: I think that ends up addressing your concern.
<dbaron> jensimmons: I'm not a fan of the try blocks, but maybe a discussion for a different day.
<fantasai> +!
<fantasai> s/!/1
<nicole> +1
<dbaron> TabAtkins: We have use cases that you need the try blocks for, but ideally we should make it so 80% of cases don't need to touch that.
<nicole> q+
<dbaron> jensimmons: I wonder if we can iterate to something where people can do the complicated stuff without try blocks.
<astearns> ack xiaochengh
<astearns> ack fantasai
<Zakim> fantasai, you wanted to ask about relationship to position-fallback
<dbaron> xiaochengh: Wanted to add something to Tab's second case: a little more complicated but not arbitrary: This property can also be used in try blocks so that we can auto-generate fallback from try blocks. It doesn't eliminate all the try blocks but it can significantly reduce the length of the try list.
<dbaron> fantasai: Relationship of this to position-fallback property? Should this be a longhand of this or separate properties?
<dbaron> TabAtkins: What posiotion fallback property?
<dbaron> TabAtkins: If you don't specify position fallback, these entries get auto generated, if you do specify then you ignore it.
<dbaron> fantasai: should they then be the same property?
<dbaron> TabAtkins: maybe
<dbaron> xiaochengh: I don't think so -- position-fallback property cannot be used in a try block but this property can
<lea> in general we should avoid designing syntax that just ignores specified values, as that tends to lead to author confusion. Maybe if we combine both somehow?
<lea> q?
<dbaron> TabAtkins: setting up one try block with the things you need and then saying auto to generate a couple more, that's fair.
<fantasai> yeah, maybe make position-fallback for both
<dbaron> astearns: can we resolve to add to the draft, and add issues such as one property or two, how it works in try blocks?
<lea> I can easily see the auto values being useful both by themselves, as well as fallbacks to more specific fallbacks specified via @try blocks
<dbaron> fantasai: I think that's fine if we make it clear there's open questions.
<dbaron> RESOLVED: accept proposal in the draft with details to be worked out over time
tabatkins commented 6 months ago

After putting our heads together, @fantasai and I have this proposal for the "details to be worked out over time":

  1. We removed all the automatic magic of creating try options from the spec in #9362, so no fallback options are created unless explicitly.

  2. We switched the position-fallback property to three associated properties:

    • position-try-options which is a comma-separated list of fallback options, either named or generated from the base styles by a transform procedure.

      position-try-options: 
         none | [ <dashed-ident> | <try-tactic> ]#
      <try-tactic> = flip-block || flip-inline || flip-start
      (initial value: none)

      The try tactics defined here mirror across the block axis, inline axis, or the start-start to end-end diagonal, respectively.

      We could add "shorthand" keywords here that expand to common sets of options, such as drop-down expanding to flip-inline, flip-block, flip-block flip-inline, but we're leaving that for later right now.

    • position-try-order which specifies how to sort/prioritize the list of options: in specified order, or by largest inset-modified containing block size

      position-try-order: normal | <try-size>
      <try-size> = most-width | most-height | 
                   most-block-size | most-inline-size
      (initial value: normal)
    • position-try-final which specifies what to do if all of the options overflow the inset-modified containing block.

      position-try-final: always || [ first | <try-size> ] | hide
      (initial value: first)

      By default it'll use the last try-option that worked, and only pay attention to this property if the element is being rendered for the first time. If you specify 'always', it'll instead always use the specified option when they all fail, even if it's different from the last successful option.

      hide acts similar to a strong visibility: hidden - the element still has a laid-out box and doesn't paint or capture mouse events, but it also doesn't affect scrollable overflow of its container, and can't be overridden on children.

  3. We changed @position-fallback rules containing @try blocks to @position-try, which gives a single named style block to reference from position-try-options:

    /* old syntax */
    @position-fallback --foo {
        @try { top: anchor(outside); }
        @try { bottom: anchor(outside); }
    }
    
    /* new syntax */
    @position-try --foo-1 { top: anchor(outside); }
    @position-try --foo-2 { bottom: anchor(outside); }
  4. We added a position-try shorthand for convenience

    position-try: <'position-try-order'>? <'position-try-options'> [, <'position-try-fallback'>? ]

    For example:

    position-try: most-width flip-block, flip-block flip-inline, --foo, first;

    This:

    • specifies three style options (2 auto-generated, one provided by a @position-try --foo {...} block) in addition to the base style.
    • sorts all four of these according to which produces the widest inset-modified containing block.
    • if all the options fail on first render, choosing the first one (the widest of the four); on subsequent renders, it uses the most recent successful option (i.e. it tries to be stable).

This system consolidates the fallback system into a single, explicit list, which makes it a lot easier to understand what's happening. Authors who want to package up a particular list can stuff it into a CSS variable.

It also leaves open the possibility of e.g. inlining inset-area syntax as one of the position-try-options items, similar to the Apple proposal from last July.

yisibl commented 5 months ago

Can the new syntax do this easily?

image image

Currently, we can used:

.horizontal .tooltip {
  left: anchor(50%);
  top: auto;
  bottom: calc(anchor(top) + 3px);
  justify-self: anchor-center;
}

.vertical .tooltip {
    inset: auto;
    left: calc(anchor(right) + 3px);
    top: calc(anchor(50%));
    align-self: anchor-center;
    justify-self: auto;
}

With the current syntax, align-self and justify-self need to be interchanged.

tabatkins commented 5 months ago

Well, note that neither of those tooltip positions are compatible with "avoiding overflow", which is the strategy used to decide whether a position is good or not. In fact, they both overflow the input's bounds, and the choice of vertical vs horizontal position seems to have nothing to do with the containing block, either.

So as far as I can tell, the question is moot - that example isn't using fallback at all.

css-meeting-bot commented 5 months ago

The CSS Working Group just discussed [css-anchor-position-1] Alternative syntax for auto position fallback, and agreed to the following:

The full IRC log of that discussion <astearns> zakim, open queue
<Zakim> ok, astearns, the speaker queue is open
<bramus> TabAtkins: we had resolution to figure out details but do sth better for auto fallback
<bramus> … we figured out the details (elika and I)
<bramus> … want to hear about objections and opinions
<bramus> … first, instead of single keyword in pos fallback prop that points to multiple try blocks we now have list of keywords in pos-try-options
<dbaron> [insert whiteboard photo PXL_20240213_181237761.jpg at the end of the previous topic]
<bramus> florian: this is flipping between options on the inset area and that only or on more things?
<bramus> TabAtkins: more; 5 sets of props: inset-area, insets, margins, and sizing props, and alignment
<bramus> … old way: `@position-fallback --foo { @try { … } @try { … } @try { … } … }`
<bramus> … we would go through list 1 by 1
<bramus> … hard to reuse
<bramus> … too much indirection
<fantasai> ... not clear how the automatically-generated fallbacks were interleaved
<bramus> … new way: `@position-try --name { (styles) }`
<bramus> … you make more than 1
<bramus> … put them in `pos-try-options`
<bramus> … `pos-try-options: --one, --two, …`
<bramus> … it goes through each 1 by 1 until 1 is good
<bramus> … also takes keywords, e.g. “flip me in the block axis”
<bramus> … can be combined with custom options
<florian> q?
<florian> q+
<astearns> ack florian
<nicole> q+
<bramus> florian: if you flip to 1 of th efallbacks tha tchagnes the computed values?
<bramus> TabAtkins: yes
<vmpstr> q+
<bramus> florian: in the apple proposal there wasnt this?
<bramus> TabAtkins: they only had ability to change `inset-area`
<bramus> fantasai: now has its own property
<bramus> … like jen pointed out: if you are using inset-area then there is a lot of code you need to write. you could also say inset-area
<fantasai> as part of the list
<fantasai> i.e. position-try-options: [ <dashed-ident> | <try-tactic> | <'inset-area'> ]#
<miriam> +1 to the inset-area extension
<bramus> chrishtr: so this ia future extension?
<bramus> TabAtkins: yes
<astearns> ack nicole
<astearns> q+
<bramus> nsull: like the naming them and comma separated list. could see in a design system this getting set up and having indiv componetns use them in different orders.
<bramus> … good for reuse
<bramus> +1
<florian> q+
<bramus> nsull: be pretty cool indeed. getting rid of the try blocks is good. felt clunky.
<astearns> ack vmpstr
<bramus> vmpstr: what do the flip keywords refer to? --fo and then flip-block? do I flip the block of the foo?
<dbaron> [whiteboard erased without photo]
<bramus> TabAtkins: say base style was `inset-area: bottom`
<bramus> … will put you below anchor
<bramus> … now `@position-try --top { inset-area: top}`
<bramus> then `@postion-try --left { i-a: left }`
<bramus> … `.popup { pos-try-options: --top, --left }`
<bramus> … or could also say `flip-block` as the value
<bramus> fantasai: this is more insteresting when you are doing things other than inset area
<bramus> TabAtkins: yes …
<bramus> … so (missed) start with `flip-block, --left, --left flip-inline`
<bramus> … so (missed) start with `flip-block, --left, --left flip-x`
<bramus> chrishtr: so the flip is a shorthand?
<bramus> fantasai: a prebuilt try block basically
<bramus> florian: the flip thing not only flips i-a but also margins, insets – the whole set of props
<astearns> (and whatever we end up adding for anchors)
<bramus> TabAtkins: e.g. a margin-top that become margin-bottom upon flipping
<astearns> q?
<bramus> vmpstr: so if you add just flip-x then i twould only filp the base sstyles across the x
<bramus> TabAtkins: yes
<bramus> vmpstr: and flip-start
<bramus> TabAtkins: would flip the margin-top to bottom e tall
<bramus> chrishtr: so 4 keywords?
<bramus> TabAtkins: no … more (missed)
<bramus> chrishtr: so 4 diagonals?
<bramus> TabAtkins: only 1 becase you can combine with other keywords
<bramus> [whiteboard erased without photo]
<bramus> TabAtkins: new example with `pos-try-options: flip-block`
<bramus> … or `flip-start` has same output (?)
<bramus> s/has same output (?)/flips across the diagonal
<bramus> then `flip-block flip-start` will first flip across the block and then the diagonal
<bramus> s/then/…then/
<bramus> chrishtr: and in RTL?
<bramus> TabAtkins: you get the other diagonal
<bramus> chrishtr: why is it called start?
<bramus> TabAtkins: only flipping the start values, not the block values
<vmpstr> is there a "rotate"
<astearns> q?
<bramus> … not married to the name
<bramus> nsull: though it was a good name
<bramus> … what about flip-inline?
<bramus> TabAtkins: only flips the inline values
<bramus> chrishtr: so `pos-try: --name` and special keywords?
<bramus> fantasai: list of options
<dbaron> [whiteboard photo PXL_20240213_183159888.jpg]
<bramus> TabAtkins: name blocks or keywords or …
<bramus> fantasai: can also put inset-area in there int he future
<fantasai> So proposed syntax would be
<bramus> astearns: concerned about it being a list property
<dholbert> q+
<fantasai> position-try-options: none | [ <dashed-ident> || <try-tactic> | <'inset-area'> ]#
<astearns> ack astearns
<fantasai> where <try-tactic> = flip-x | flip-y | flip-inline | flip-block | flip-start
<bramus> TabAtkins: better than the prvious list which was expressed via at-rules
<astearns> ack florian
<bramus> … if and when we get addidtive cascade then this current sytnax will work better
<bramus> florian: if we include the syntax extension to have `inset-area` so that you can skip at-rules then this looks like a good propopsal
<bramus> … flip-start naming is weakest part though. needs bikeshedding
<bramus> … cmoputed value of inset-area propably gonna be a lot simplear
<bradk> q+
<bramus> … right now the active what is the computed value (missed)
<bramus> TabAtkins: if we expose things in cqs we now have a single clearing house of what the list of ?? is. much clear to communicate to author and let them do CQ against it
<bramus> fantasai: (backs that up)
<astearns> ack dholbert
<bramus> dholbert: when flipping across the diagonal from start-start, is there an optoin to do from the opposite diagonal start-end?
<fantasai> s/(backs that up)/having a single list of options rather than multiple sources that automatically generate and interleave in non-obvious ways is imho a lot better/
<kbabbitt> q+
<bramus> TabAtkins: no. you can combine keywords.
<florian> s/if we include the syntax extension to have inset-area so that you can skip at-rules/especially if we include the syntax extension to have inset-area so that you can skip at-rules when you don't need them, and include the || between dashed-indent and try-tactic
<bramus> dholbert: is there an equivalent keyword for flip-start htat uses similar longer names to th eother diag flip option?
<bramus> TabAtkins: no, bc block inline and start toegheter are the start for all other permutations
<florian> s/cmoputed value of inset-area propably gonna be a lot simplear/computed value of inset-area becomes a lot simpler: the active mode is the computed value
<bramus> … not sure how you would spell them longer
<bramus> dholbert: would be nice to reason about
<bramus> TabAtkins: open for good names
<bramus> dholbert: 2nd q: for --name options: would the syntax allow you use a variable that includes a --name?
<bramus> TabAtkins: so `@position-try --left { left:var(--foo) }`?
<bramus> dholbert: no.
<bramus> … in the @position-try name
<bramus> s/@position-try name/position-try-options property
<bramus> TabAtkins: yes, you can assign to variable
<astearns> ack bradk
<bramus> bradk: if it flips, then a bottom margin would become top. Is it just marign or also border and padding?
<bramus> TabAtkins: insets, inset-area, alignment, margins, and sizing props (width and height)
<bramus> … not doing padding and border because implementation reasons
<bramus> iank_: it is something like an internal thing of that box, vs the rest is external
<bramus> … (??) tables. similar to border … painting properties
<bramus> florian: could you use CQs to figure which configuration you are in and based ont hat change padding?
<bramus> TabAtkins: yes. not explored yet but on our list to do … having a way to query whcih fallback you are in
<dholbert> My example with css variables above (which should just work, per discussion) was something like: @position-try --left { ... } .my-tooltip { --brand-start: --left; position-try-options: var(--brand-start); }
<bramus> … bare min change styling of children
<astearns> idly wonders if there might be a way of specifying a combination of physical and logical properties such that some flip and some do not
<bramus> … if querying based on fallback set you are in and you are not allowed to change that, then we dont have cyclic prolbems to style yourself either
<bramus> … worst case only children
<bramus> bradk: so you could havbe box shadow change
<bramus> TabAtkins: yes
<bramus> iank_: also ties into tether problem … change pos and style of the tether based on what position it is in
<bramus> florian: interesting … with auto flip you can change a bunch of vlaues to allow them to transition … would a CQ also allow that? E.g. box shadow
<bramus> … is the CQ type of flipping break animation?
<bramus> TabAtkins: no; computed value will change and it will transition
<bramus> … again, completely unspecified today but it works
<astearns> ack kbabbitt
<bramus> kbabbitt: q about grammar …
<bramus> … double || there for dashed items? Is that intended?
<bramus> TabAtkins: no, doenst allow you to repeat … only pick multiple from the set
<vmpstr> i'm wondering if flip-start should be transpose, and whether there's any value in rotate-cw and rotate-ccw as additional values to rotate clock and counter-clock wise
<bramus> kbabbitt: there was an optoin about wher eyou could hav emultiple try tactcis, but dont see how that is possible with current grammar
<bramus> TabAtkins: (clarifies syntax)
<bramus> kbabbitt: oh, got it
<bramus> astearns: want to resolve?
<bramus> TabAtkins: want to talk about next aspect, which is not this complicated
<bramus> … resolution is for later on
<bramus> TabAtkins: so next part, a lot of JS libs allow you to put yourself into area with the most space.
<bramus> … we built that into the spec
<bramus> … `pos-try-order` property
<bramus> … couple of keywords; which size you care about
<bramus> … `most-*`
<bramus> florian: no `most-area`?
<bramus> TabAtkins: no because (missed)
<vmpstr> s/(missed)/ill defined and nobody else offers it/
<bramus> … example: `pos-try-options: --top, --bottom`
<bramus> … want to start with biggest
<bramus> … so `pos-try-order: most-height`
<bramus> … currently, to match behavior of libraries we will also try base styles files and only if there is overflow we try the rest
<bramus> … could add options here to include base style as one of the reorderable options
<florian> q+
<bramus> … more expensive because you need to do mutliple stylings
<bramus> … but possible
<bramus> … achiecable by forcing overflow in base styles
<bramus> fantasai: dont do that! terrible!
<bramus> TabAtkins: could also add option to skip basesyles immediately
<bramus> … gonna skip `pos-try-final` for now
<bramus> … so `pos-try-options` and `pos-try-order` to do ???
<bramus> fantasai: not the size of the pos box we are checking but size of IMCB so you dont have to do layout
<bramus> TabAtkins: yes, to resolve the insets
<bramus> fantasai: show the shorthand
<bramus> TabAtkins: that is order followed by options
<bramus> … “ i want the most-width of these”
<bramus> … `position-try` property
<astearns> ack florian
<astearns> q+
<bramus> florian: in itself pickin gorder makes sense. confused about if you pick the one with tallest heigth first, then you are only gonna pick from smaller options?
<bramus> … might vary on the other axis though
<bramus> … what happens if it fits nowehere?
<bramus> TabAtkins: you can adjust properties in the otpions (e.g. max-ehgith) so you might fit in smaller space
<bramus> …when nothing fits you stick with last successful fallback that we used, so you remain stable if nothing works.
<bramus> … there is `position-try-final` to control this
<bramus> … lets you opt in to falling back to largest or first option
<bramus> … (and more)
<bramus> … can also set it to `hide`
<bramus> … vmpstr has use case for it
<bramus> florian: I dont know if theyse keywords if everything we ever need, but seems good extension points
<bramus> … eg “go clockwise”
<bramus> astearns: look at the shorthand
<dholbert> q+
<bramus> … how wedded are you to having the shorthand?
<bramus> … i dont get the idea that it is going to use the first one if it fits here in this example
<bramus> TabAtkins: it will take ??
<bramus> fantasai: when we first designed this oy have base styles and various fallbacks in optoin list whih concat in complete list: base + transforms
<bramus> … and then you re-sort
<bramus> … so you propose to only re-sort the fallbacks?
<bramus> TabAtkins: by default
<bramus> … bc that is how material design’s anchor lib work and their arg makes sense
<bramus> astearns: so pos-try is only listing th e alternats
<bramus> nsull: do you expect authors to ???
<bramus> TabAtkins: they might
<bramus> … excited to see what we can make easier
<bramus> … similar to well named common cases.
<astearns> ack dholbert
<astearns> ack astearns
<bramus> dholbert: so you choose either most-width or most-height?
<bramus> … no secondarily option for “most space”?
<bramus> TabAtkins: not currently. use-cases needed.
<bramus> … havent seen this in js libs
<bramus> … do we wan tto resolve on this as basis for new model?
<bramus> astearns: can do indiv parts?
<bramus> TabAtkins: would like all
<bramus> florian: to be clear: only as a starting point
<bramus> TabAtkins: yes
<bramus> fantasai: yes
<bramus> nsull: seems like a good improvement overall
<bramus> florian: current thing does not include `inset-area` built in
<bramus> fantasai: can build that in
<bramus> fantasai: proposed resolution: adopt `position-try-*` with addition of inset-area into pos-try-opts
<bramus> astearns: concerns?
<bramus> astearns: objections?
<una> LGTM
<bramus> RESOLVED: adopt `position-try-*` with addition of inset-area into pos-try-opts
mfreed7 commented 5 months ago

Should be addressed by the current draft spec.