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-pseudo-5] ::text / ::text-node pseudoelement #2208

Open
Nadya678 opened this issue Jan 19, 2018 · 36 comments
Open

[css-pseudo-5] ::text / ::text-node pseudoelement #2208

Nadya678 opened this issue Jan 19, 2018 · 36 comments

Comments

@Nadya678
Copy link

https://drafts.csswg.org/selectors-4/

Please add ::text or ::text-node selector to select all text nodes inside any element
for example for

<p>
   #text1: Here is text
   <span>
      #text2: Here also is text
   </span>
   #text3: Here is second text in the paragraph
</p>

the p::text will select #text1 and #text3 and the pseudoelement can be styled like ::after and ::before (without need to set "content").

@js-choi
Copy link

js-choi commented Jan 19, 2018

I’ve also been thinking about pseudo-elements for anonymous boxes, first to the WICG to be eventually moved here. I talked about it at bit at #2084 (comment) – and I’ve have been writing a strawman when I can. I could find barely any prior mention of this idea before, when I searched on the web and in the old mailing lists.

For instance, a pseudo-element for anonymous block boxes would be generally useful in other cases as an approximate analogue to HTML’s paragraphs, e.g., anonymous-block “paragraphs” such as those in <h1>First block</h1> This is the <em>second</em> block in this example. <p>This is the third.</p> <ul> <li>Fourth.</li> <li> <p>Fifth.</p> <p>Sixth.</p> </li> </ul>. Such pseudo-elements, however, might have broad ramifications for rendering performance, so I had planned on courting authors in the WICG before the CSSWG to bolster my case. This is the first mention of this idea I have yet seen elsewhere on the web, so it’s good to have another use case.

My own biggest use case would be uniformly styling the boxes of paragraph text in <p>s, <li>s, <dt>s and <dd>s, <th>s and <td>s, <h1>s, and so forth, even when they’re arbitrarily nested. @AmeliaBR wrote about some other use cases in #2084 (comment). These include selecting <details> text content not within a <summary> element and fallback text for things like <meter> and <progress>.

I don’t know if I should keep working on my WICG strawman if there’s going to be discussion here. I would be happy with any discussion: I’m certainly still surprised that there’s been barely any discussion about this idea over the past thirty years.

@AmeliaBR
Copy link
Contributor

I'm definitely supportive of the idea in general.

A more detailed proposal with examples would probably be helpful in gaining more supporters, so if you're already working on something @js-choi, please don't stop just because this has now been formally raised as a working group issues.

Some things to think of:

  • An anonymous block is different from a text node, since it can include inline elements. (In your code snippet, the text nodes that are direct children of the body would be "This is the" and "block in this example.") If there are use-cases for both, then maybe there needs to be more than one selector.
  • Woule a pseudoelement selector only select direct-child text nodes for the element it is attached to, or any descendent text nodes?
  • Whitespace-only text nodes are treated differently than content nodes in CSS layout, so would probably need to be distinguished.

Which are all probably good prompts for a WICG discussion if you want to start one.

@Nadya678
Copy link
Author

Nadya678 commented Jan 19, 2018

  1. In my opinion ::text should select only direct childs. we can write in my example:
    p::text, p ::text /* note a space before ::*/
    to select all text nodes.
  2. Yes, whitespace nodes should be treated in other manner.

@emilio
Copy link
Collaborator

emilio commented Jan 19, 2018

It's unclear about what most of the CSS properties would mean on a text-node, though, right? Specially non-inheriting ones.

@Nadya678
Copy link
Author

In my opinion all styles including absolute positioning. But ::text::before forbidden.

@emilio
Copy link
Collaborator

emilio commented Jan 19, 2018

What does an absolutely positioned text-node mean? Can you set display: block on a text-node? What about display: none? What happens to text layout when you combine stuff using display: contents and such?

@tabatkins
Copy link
Member

There's two ways to go about this. One is to style the text node itself - only a subset of the CSS properties would apply, those that work on text. (More or less just the properties that are inherited, but there are some inherited properties that wouldn't apply.) We'd have to define the precedence of this vs ::first-line and ::first-letter.

The second is to create an anonymous box around the text node and style that. In this case all of CSS applies. There is a minor problem here, tho - one of the use-cases for this is to solve issue 2 in the Scoping spec, and it's not totally clear where in the flat tree the anonymous box would be.

@js-choi
Copy link

js-choi commented Jan 20, 2018

@tabatkins What about selecting anonymous inline blocks? They’re not exactly the same as text nodes (especially regarding positioning), but they overlap quite a bit. My impression had been that those are already being generated by current agents and thus would not necessitate creating new boxes.

@Nadya678
Copy link
Author

Nadya678 commented Jan 22, 2018

It's unclear about what most of the CSS properties would mean on a text-node, though, right? Specially non-inheriting ones.

Works it like ::after and ::before.

::text
{
   display:block;
   border:2px solid red;
}

will be shown identically like:

::before
{
   content:"the node text"; 
   display:block;
   border:2px solid red;
}

I think it is the easiest solution.

display: none

The text is removed and not shown.

display: contents

Need clarification and any idea. It can be by exception treated as display:inline

The second is to create an anonymous box...

Yes, in my opinion the anonymous inline box will be the solution. Only exception to styles that cannot be applied like display:contents. Please note, if the element <p/> in my example will be styled display:flex; the text nodes are switched to show like display:block.

@Loirooriol
Copy link
Contributor

I have desired something like this several times when I have <div>Foo: <textarea></textarea></div> and I want to vertically center the text with the textarea via vertical-align: middle, but there is no way to select the text.

Always wrapping text runs inside new boxes may be a breaking change, e.g. boxes inside a flex container become flex items, so this would prevent sequences of text runs to be wrapped inside a single anonymous flex item. CSS Flexbox could discriminate boxes generated by ::text from all the other ones, but this would be confusing and inconsistent.

It has also been suggested to inject the styles into anonymous boxes, but I think this is a bad idea because the cascade is supposed to happen in the element tree, and I prefer to avoid another fiasco like ::first-line.

I think what makes more sense would be:

  • Text nodes (or sequences of text nodes?) in the DOM tree are wrapped inside a ::text pseudo-element in the CSS element tree.
  • These ::text pseudo-elements must act as if they were assigned display: contents via a rule in the UA origin. Therefore, they do not generate any boxes and are treated as if they had been replaced with their children, which would be the current behavior. (There is precedence for this)
  • This must be possible to override via display, so they do generate boxes if desired. But maybe mark this at-risk.

@Nadya678
Copy link
Author

Nadya678 commented Jan 22, 2018

What is formal initial display value for ::after if the display, float and position are not defined?

BTW ::first-letter is greater fiasco than ::first-line

@Loirooriol
Copy link
Contributor

@Nadya678 ::before and ::after have the initial display: inline by default. But they also have content: none, which behaves like display: none.

@gsnedders gsnedders added the selectors-4 Current Work label Jan 22, 2018
@Nadya678
Copy link
Author

OK. Thus here one exception: ::text - default display:contents.

@fantasai fantasai removed the selectors-4 Current Work label Jan 27, 2018
@fantasai fantasai changed the title [css-selectors-4] ::text / ::text-node pseudoelement [css-pseudo-5] ::text / ::text-node pseudoelement Jan 27, 2018
@inoas
Copy link

inoas commented Mar 8, 2018

I wasn't aware of this ticket existing when I wrote this: #2406 (comment)

The basic take-away is that I don't think a text node selector should be a pseudo element prefixed with colon(s) - that creates all kind of problems with specificity imho.

Instead it should be like an invisible p-tag with slightly less specificity than tags that auto opens on text and auto closes before any new block context / when the parent element closes.

@ByteEater-pl
Copy link

I believe some of the above thinking too tightly tied to an artifact in the DOM (that never results from parsing) which is the possibility of having empty or adjacent text nodes. Let's have CSS, if in a DOM-based implementation of the host language, pretend that document.normalize() was performed before styling. (And do something sensible for APIs using CSS selectors.)

@Loirooriol
Copy link
Contributor

@ByteEater-pl It could be tied to the concept of text run:

each contiguous sequence of sibling text nodes generates a text run [...]. If the sequence contains no text, however, it does not generate a text run.

@mauriciogior
Copy link

mauriciogior commented Jun 28, 2021

A pseudo element like ::text would have its benefits, but I'm still trying to figure out how to use it in the following use case:

HTML:

<p class="message">
     Hi there
     <span class="emoji-native" id="1">😋</span>
     this is cool!
     <span class="emoji-native" id="2">😍</span>
     <span class="emoji-native" id="3">😍</span>
<p>

CSS:

.emoji-native {
      margin: 0 3px;
}
.emoji-native + .emoji-native {
      margin-left: 0px;
}

Basically in this example I want to consider ::text as a node between the two .emoji-native nodes when using the + operator, but it turns out it simply ignores it and adds that CSS on both ID 2 and 3.

To be honest I don't see a syntax that would make this work, this feels out of context. I would vouch though for a new kind of element that would represent displaced text written directly into the source code, or even a simple "invisible" <text> HTML tag that represents all texts that are siblings to actual nodes.

@Loirooriol
Copy link
Contributor

@mauriciogior No, adding ::text shouldn't break existing combinators or selectors, they would continue to skip text nodes

<p class="message">                   <!-- p -->
  Hi there                            <!-- p::text -->
  <span class="emoji-native" id="1">  <!-- p > :nth-child(1) -->
    😋                                <!-- p > :nth-child(1)::text -->
  </span>
  this is cool!                       <!-- p::text -->
  <span class="emoji-native" id="2">  <!-- p > :nth-child(2) -->
    😍                                <!-- p > :nth-child(2)::text -->
  </span>
  <span class="emoji-native" id="3">  <!-- p > :nth-child(3) -->
    😍                                <!-- p > :nth-child(3)::text -->
  </span>
</p>

If what you want is selecting a .emoji-native whose previous sibling node (including text) is a .emoji-native element, then I don't think that's covered by this feature. You would need a new kind of combinator.

@jonathantneal
Copy link
Contributor

jonathantneal commented Oct 11, 2022

This selector would have been useful to me when targeting slotted text nodes, created either from implicit <slot> elements, or created from programatic, manual slot assignments. The CSS ::slotted(*) selector does not currently select text nodes.

I would also like to point out some prior art. At the time of this writing, other fully or partially shipping CSS text selectors or text-capable selectors include ::first-letter, ::first-line, ::selection, ::target-text, ::spelling-error, ::grammar-error, ::before, and ::after.

As a related aside, ::slotted(*)::before and ::slotted(*)::after can be used to target generated, slotted ‘before’ and ‘after’ text content. I am a little confused as to why the ::before or ::after selector comes after the ::slotted selector rather than inside of it. I could imagine that raises some parity issues for something like ::slotted(::text) or even ::slotted(::first-letter), as either those selectors may include ::before content.

@samuelbradshaw
Copy link

samuelbradshaw commented Oct 20, 2022

Instead it should be like an invisible p-tag with slightly less specificity than tags that auto opens on text and auto closes before any new block context / when the parent element closes.

This is an interesting idea, especially since the closing tag for <p> is already optional. However, there may be some issues with backwards compatibility. If my website uses CSS to add a background color on all <p> elements (for example), suddenly my website would have a lot more background color than I intended. Also, <p> is a block element, and the text node may not be.

@mauriciogior mentions a new <text> element – if we went that route, both the start tag and end tag could be optional. But I guess I'm not clear on what the difference would be in practice between an implied <text> element or a ::text pseudo-element.

Another downside of an implied element is that it would get confusing when using positional pseudo-classes, like :nth-child().

@dotterian
Copy link

I think this selector will enable pretty cool features in combination with :has.
For example:

.button {
  display: flex;
  height: 40px;
  padding: 8px 16px;
  column-gap: 8px;
  align-items: center;
}

.button:has(svg):not(:has(::text)) {
  width: 40px;
  padding: 0;
  justify-content: center;
}

This will make one class for regular buttons with icon and text and for square buttons if it contains only icon.
For now we have to make additional class for square buttons and I wish we could have more smart, "component-like" classes.

@Loirooriol
Copy link
Contributor

While that may be possible, pseudo-elements are currently invalid inside :has(), so it would need special handling.

Also note that ::text means *::text, so :has(::text) would match elements that have some descendant element that has a text node child. As I said in #7463 (comment), you probably want :has(:> text).

Also note that ::text would presumably affect white space, so it may not work as desired if you have

<button>
  <svg>...</svg>
</button>

@dotterian
Copy link

Imo, in case of button *::text is ok. Since there'll be no descendants, it's simply svg + text node.
But that doesn't excuse my bad syntax.

On the topic of whitespaces: I think ::text should ignore "empty" text nodes, which contain only whitespace characters. As mentioned in this comment

@Loirooriol
Copy link
Contributor

That comment refers to empty text nodes like new Text(), white space characters are text, and ::text { white-space: pre } seems reasonable.

@dotterian
Copy link

Is somehing like ::text:empty possible? It could solve the whitespace problem, according to CSS Selectors Level 4 Draft

@Loirooriol
Copy link
Contributor

Seems possible as long as the content property is not able to change the text (otherwise it would be circular).

@akira-cn
Copy link

akira-cn commented Dec 25, 2023

@mauriciogior

I also want to consider ::text as a real node when using the + operator.

This will be useful while using the streaming mode calling the ChatGPT's completion api with an asynchronous function call.

When ChatGPT is outputting content in Streaming mode, it usually involves text nodes. At this time, if it needs to draw an image, it will pause to execute a function call. During this, a Loading sign should be displayed in the UI. The simplest way to do this is to add a <span class="loading"/> tag within its streaming content. Once the image is loaded and it continues to output other text content, this tag should be set to display: none.

p .loading:has(+::text) {
    display: none;
}

@contentfree
Copy link

Having ::text (as a real node) would also be supremely useful when doing something seemingly simple like hiding text next to an icon. Consider (using Font Awesome):

<a href="#"><i class="fa fa-gear"></i> Edit</a>

Currently, one must do tricks with overflow: hidden and width (or several varieties of this hack) to show just the icon. It would be wonderful to just have a rule a ::text { display: none; } (or better yet: i.fa + ::text { display: none; }

@Crissov
Copy link
Contributor

Crissov commented Jan 12, 2024

Looks like display: icon which never came to be. https://www.w3.org/TR/2012/WD-css3-ui-20120117/#element-icons

@Loirooriol
Copy link
Contributor

@contentfree a ::text means a *::text, i.e. the text pseudo-element originated by an element which is a descendant of a. And i.fa + ::text means i.fa + *::text, i.e. the text pseudo-element originated by the next sibling element of i.fa.

So with this proposal you would actually need a::text { display: none }, and can't do different things for text nodes that precede or follow i.fa.

@emilio
Copy link
Collaborator

emilio commented Jan 12, 2024

Having ::text (as a real node) would also be supremely useful when doing something seemingly simple like hiding text next to an icon. Consider (using Font Awesome):

<a href="#"><i class="fa fa-gear"></i> Edit</a>

Currently, one must do tricks with overflow: hidden and width (or several varieties of this hack) to show just the icon. It would be wonderful to just have a rule a ::text { display: none; } (or better yet: i.fa + ::text { display: none; }

content: none ought to work for this, right? But when we implemented it wasn't web compatible, we'd need a separate keyword

@contentfree
Copy link

@contentfree a ::text means a *::text, i.e. the text pseudo-element originated by an element which is a descendant of a.

That's why I qualified it with "as a real node".

Jumping back to the comment at #2208 (comment) and its example: It seems like some mechanism (maybe not ::text?) to include the current element's text nodes as actual children would allow for i.fa + <text node> or targeting the text node after the first span.emoji-native element in @mauriciogior's example. (Would a::text:last-child work? That would solve for my icon example, I believe.)

@SebastianZ
Copy link
Contributor

So with this proposal you would actually need a::text { display: none }, and can't do different things for text nodes that precede or follow i.fa.

Correct, but you could still target the text node next to the icon via a:has(> .fa)::text.
Also, when adjacent sibling pseudo-element combinator suggested in #7346 becomes reality, one could write i.fa :+ text instead.

If we introduce a ::text pseudo-element for that, I'd suggest to also add a functional version of it to target specific text nodes. That function takes an An+B notation to restrict it to specific nodes. So applying p::text(1) to the example in the initial comment would target the text node "Here is text".
Alternatively, we could define that the different :nth-*() pseudo-classes, when applied to text nodes, only refer to text nodes. So the first text node would then be targeted via p::text:nth-child(1).

I also want to point out that this could be a potential performance footgun, because as @Loirooriol pointed out several times, ::text means *::text, so it targets all text nodes on a page. So authors might be inclined to define ::text { color: black; } instead of body { color: black; } and relying on the cascade.

Sebastian

@contentfree
Copy link

Just wanted to add another example where this would be useful. I'm pretty sure there's currently no (CSS-only) way to distinguish between these to only add right-margin to the img in the first div:

<div>
  <img /> 
  Some label
</div>

<div>
  <img />
</div>

@Loirooriol
Copy link
Contributor

@contentfree That seems a different feature, maybe :has(:+ text) from #7346 as Sebastian said.

But anyways, div { margin-trim: inline } img { margin-right: 1em } seems a better approach. See https://drafts.csswg.org/css-box-4/#margin-trim

@starball5
Copy link

Related on Stack Overflow: Is there a CSS selector for text nodes?

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