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-text-4] add 'avoid' to text-wrap-mode #9448

Open
bradkemper opened this issue Oct 9, 2023 · 10 comments
Open

[css-text-4] add 'avoid' to text-wrap-mode #9448

bradkemper opened this issue Oct 9, 2023 · 10 comments

Comments

@bradkemper
Copy link
Contributor

bradkemper commented Oct 9, 2023

in #9102 (comment)_, we are working on the name of the onoff longhand property for white-space or text-wrap. The values for text-wrap-mode are already decided as wrap | nowrap.

I am suggesting a third value be added, avoid-wrap (or maybe just avoid). This would take the place of a separate (binary) wrap-inside property. It would also make the text-wrap-mode property (whatever we call it) not be just a binary toggle, because it would have three values now.

I think this is easier for authors than learning a new property (it's getting complicated enough anyway). Since wrap-inside: avoid is basically a relaxed version of text-wrap-mode: nowrap (provisional property name), I think it fits more naturally in the same property.

One implication of making this change is that the property value would be inherited, like the other values of the text-wrap-mode property. But I think this is better anyway, so that if a paragraph is avoiding wraps where possible, so would its children (such as a long <b> element).

@bradkemper bradkemper changed the title [css-text-4] [css-text-4] add 'avoid' to write-space-wrap Oct 9, 2023
@bradkemper
Copy link
Contributor Author

That's probably not the best example for why it should inherit, since you probably wouldn't set this on a block. A better example might be a link that has some bold text inside it.

@fantasai
Copy link
Collaborator

If you make it an inheritable property then you only get one level of prioritization: the entire run of consecutive avoid content avoids wrapping, but there can't be any prioritization within that. Might be fine, but something to consider.

@bradkemper bradkemper changed the title [css-text-4] add 'avoid' to write-space-wrap [css-text-4] add 'avoid' to text-wrap-wrap Oct 11, 2023
@bradkemper
Copy link
Contributor Author

@fantasai Yeah, I guess it's about which thing would be more annoying: using a new property and having to set it for all the descendants you wanted to apply it to, versus needing to explicitly undo that for descendants that shouldn't be avoid.

To me, the former would be worse, but maybe there are use cases that could convince me otherwise?

I also get the desire to have the value space of wrap-before/after/inside match that of break-before/after/inside.

@frivoal
Copy link
Collaborator

frivoal commented Oct 13, 2023

I guess it's about which thing would be more annoying: using a new property and having to set it for all the descendants you wanted to apply it to, versus needing to explicitly undo that for descendants that shouldn't be avoid.

I suspect this is a misunderstanding of how wrap-inside: avoid works. You do not need to set it for all the descendants of an element you want it to apply to.

Say you have something like this:

<div>…<em>…<span>…</span>…</em>…</div>

If you apply wrap-inside: avoid to the <em> element, then wrapping is disabled for the whole element, including descendants like the <span>. Unless that would overflow, in which case you give up, and wrap normally.

If you apply wrap-inside: avoid to the <em> and the <span> element, then wrapping is disabled for the whole <em> element, including descendants like the <span>. Unless the <em> would overflow, in which case you give up, and wrap normally for the parts of the <em> outside the <span>, but you continue to disable wrapping within the <span>. Unless that would overflow too.

In a way, this is similar to text-decoration: it's not inherited, but it covers the whole subtree rooted at the element you target.

@frivoal
Copy link
Collaborator

frivoal commented Oct 13, 2023

relatedly:

if a paragraph is avoiding wraps where possible, so would its children (such as a long <b> element)

You cannot set up wrap-inside: avoid on a paragraph, as that's a property that only applies to inline elements. And for good reason: it would not make sense to say of a paragraph "don't wrap, unless that would lead to overflow, in which case do wrap". That's exactly the same as allowing wrapping. It does make sense, however, on an inline which is inside of a block which is allowed to wrap.

@bradkemper
Copy link
Contributor Author

In a way, this is similar to text-decoration: it's not inherited, but it covers the whole subtree rooted at the element you target.

That didn't seem obvious in the spec language. In reading, your response, I was thinking that sounds like text-decoration, but I'm not seeing how that's better. I've always found that aspect of text decoration confounding (and initially confusing), because I couldn't then turn the underline off for any descendants if it was on in an ancestor. It still got underlined, even with a computed value of none.

Is that how it would be with a non-inherited avoid too? I hope to be able to say "try to avoid breaking this span of text, but if it doesn't fit on the line, break it within this sub-span." With inheritance, it seems a more clear that I can do that, and won't be absolutely bound by a decision of an ancestor, like with text-decoration.

@bradkemper
Copy link
Contributor Author

You cannot set up wrap-inside: avoid on a paragraph, as that's a property that only applies to inline elements.

Yes, you are right. I figured that out and corrected myself in the next comment after that.

@bradkemper
Copy link
Contributor Author

bradkemper commented Oct 15, 2023

Stated another way:

If I have white-space: nowrap on a parent, and white-space: normal on a child, the child can wrap. This is because the non-wrapping behavior is only applied if the computed value after inheritance is nowrap. That's what I'm looking for with text-wrap-mode: avoid, so they all work in a consistent way, which is also the way I would prefer. So text-wrap-mode: avoid would only avoid wrapping on the text that had or inherited that value, and it would inherit by default.

I'm not seeing the downside here. This seems much preferable to emulating the oddities of text-decoration, and how it affects from a distance children that don't inherit its value.

@bradkemper bradkemper changed the title [css-text-4] add 'avoid' to text-wrap-wrap [css-text-4] add 'avoid' to text-wrap-mode Oct 15, 2023
@bb010g
Copy link

bb010g commented Dec 15, 2023

Does an inherited text-wrap-mode: avoid still allow the children of an element that already avoids text wrapping to more strongly avoid wrapping? Take the following HTML as an example, using non-inheriting wrap-inside: avoid, and presuming a monospace font:

<span><span style="wrap-inside: avoid;">a b c d <span style="wrap-inside: avoid;">e f g</span></span> h <span style="wrap-inside: avoid;"><span style="wrap-inside: avoid;">i j k</span><span style="wrap-inside: avoid;">l m n</span> o</span></span>
  1. Let's start with a max-content width top-level <span>:
    a b c d e f g h i j k l m n o
    
  2. As we shrink the container, text has to wrap. Line breaking occurs around h, as the spaces around it are the only ones without wrap-style: avoid. First:
    a b c d e f g h
    i j k l m n o
    
  3. And right after, to avoid line-breaking inside i j k l m n o:
    a b c d e f g
    h
    i j k l m n o
    
  4. Next, line breaking is required inside both a b c d e f g and i j k l m n o at the same time. Line breaking occurs before e f g (to avoid wrapping inside that nested wrap-style: avoid span) and before o.
    a b c d
    e f g
    h
    i j k l m n
    o
    
  5. Line breaking is required inside i j k l m n, and l m n is wrap-inside: avoid:
    a b c d
    e f g
    h
    i j k
    l m n
    o
    
  6. There's a normal line break point before d:
    a b c
    d
    e f g
    h
    i j k
    l m n
    o
    
  7. Any further reductions in width would incur word breaking or overflow.

To me, this whole process feels very different from text-decoration. We're not declaring a binary state of "underline"/"don't underline" or "allow wrapping"/"don't allow wrapping", where it both makes sense to inherit a parent's state and override a parent's state, but rather we're rather declaring box-level prioritization in the line-breaking process. You could view this like font-size if you wanted to think about calculated root-relative wrap priorities, e.g. <span style="wrap-inside-relative-badness: 0.5;">a b</span> c d <span style="wrap-inside-relative-badness: 2;">e f</span> to eagerly wrap between a & b (the least bad valid line break point) and avoid wrapping between e & f (the most bad valid line break point), but that's still a non-inherited property. (Side note: nowrap is like infinite badness. It's equivalent if you never break at an infinitely bad break point.) I don't know what the equivalent of white-space: normal is for a child of a box with wrap-inside: avoid is, besides something like <span style="wrap-inside-relative-badness: 2;"><span style="wrap-inside-relative-badness: 0.5;">…</span></span>. If you can reverse the text-wrap-mode: avoid of an outer box with text-wrap-mode: wrap on an inner box, then that suggests to me that <span style="text-wrap-mode: avoid;"><span style="wrap-inside: avoid;">…</span></span> shouldn't cause the nesting behavior specified for wrap-after: avoid, and that makes text-wrap-mode: avoid much less useful, at least to me. I'd love a composable way to discourage wrapping inside CSS.

Additionally, wrap-inside: avoid parallels wrap-before: avoid. If text-wrap-mode: avoid is used, there should also be a flex-wrap-mode: avoid to parallel wrap-before: avoid-flex.

Also, something I'm not sure about from the current wrap-inside: avoid language of «If boxes with avoid are nested and the UA must break within these boxes, a break in an outer box must be used before a break within an inner box may be used.»: if a break has already occurred in an outer box, and a break is used within an inner box, may that outer break be collapsed/reversed if there's space? For example, could 4. from above be rendered as the following, where the line break between e f g and h is undone due to available space? This does cause a rendered output where the inner a b c d e f g box still uses a break while the valid break point after a b c d e f g in the outer box isn't used.

a b c d
e f g h
i j k l m n
o

@frivoal
Copy link
Collaborator

frivoal commented Dec 18, 2023

Does an inherited text-wrap-mode: avoid still allow the children of an element that already avoids text wrapping to more strongly avoiding wrapping?

Not in any obvious way I can think of, and that ability is the reason why it is designed the way it is at the moment.

Also, something I'm not sure about from the current wrap-inside: avoid language of «If boxes with avoid are nested and the UA must break within these boxes, a break in an outer box must be used before a break within an inner box may be used.»: if a break has already occurred in an outer box, and a break is used within an inner box, may that outer break be collapsed/reversed if there's space?

I agree that is not 100% clear from the spec. I'd say no, you don't get skip/undo breaking on the outer box just because you had to break in the inner one, that would go against the sense of hierarchy.

In example 4, I think you could take the break after h rather than before, because either breaks are at at the same level of hierarchy. What you don't get to do is put h and i on the same line in case 5 (assuming the h somehow still starts the line: let's pretend for instance that there are two hs at the same level in the tree): even though the break between k and l mean you could now fit h and i j k onto one line, you don't get to undo the break between h and i.

But I guess that's open to interpretation, and that there's (at least) two questions:

  • if an inner box had to be broken, and there's space left over on the end of the line after it, can you fill that space with subsequent content, or do you have to first take a break at the end-edge of that inner box, to respect the hierarchy? (that's case 4 as you were talking about)
    → I'd say it's fine, you can fill that space. (If the thing you're filling it with would itself fit.)
  • When an inner box has to be broken be cause it is too long to fit a line even if it is alone in it, are you allowed to place the now shorter first chunk of it on an earlier line if there's room enough for that?
    → I'd say no, that breaks the hierarchy.

But I do realize that this preference of mine isn't symmetric. I think I'd be more willing to change my mind on the first one than on the second.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants