w3c / csswg-drafts

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

[css-selectors] Standardize `:(‑moz‑)first‑node` and `:(‑moz‑)last‑node` #3216

Open ExE-Boss opened 5 years ago

ExE-Boss commented 5 years ago

Right now, CSS Selectors 4 defines :first-child and :last-child, which works as such for the following:

span:last-child {
  background-color: lime;
}
<div>
  <span>This doesn’t match.</span>
  <span>This matches.</span>
</div>

<div>
  <span>This matches.</span>
  Some text after.
</div>

For :first-node and :last-node, this would work as follows:

span:last-node {
  background-color: lime;
}
<div>
  <span>This doesn’t match.</span>
  <span>This matches.</span>
  <!-- White-space and comments are ignored when
     - determining whether :last-node matches -->
</div>

<div>
  <span>This doesn’t match.</span>
  Some text after.
</div>

Mozilla Firefox already implements the previous in prefixed form and exposes it to the web.

emilio commented 5 years ago

FWIW, these selectors are somewhat slow, and I planned on trying to hide them from content when I had the chance...

upsuper commented 5 years ago

Selectors mainly work on element level, and extending them to node level may cause some confusing, and ignoring white-space and comments isn't really what "last node" should mean...

emilio commented 5 years ago

Selectors mainly work on element level, and extending them to node level may cause some confusing, and ignoring white-space and comments isn't really what "last node" should mean...

I agree, the only reason these selectors exist at all in Gecko is to implement quirks.

SelenIT commented 5 years ago

The demand to differentiate child elements that don't have any text before them from those that do seems to occur quite often (especially in styling user-generated content, in designing components that can be inserted into arbitrary environment, some more use cases are mentioned in #2208). So it would be great if there would be a standard tool for this, even if the current Gecko's experimental implementation is not perfect (though it's much better than nothing:).

ExE-Boss commented 5 years ago

The demand to differentiate child elements that don't have any text before them from those that do seems to occur quite often (especially in styling user-generated content)

For me it’s elements that don’t have any text after them (eg. the last paragraph in a comment shouldn’t have a bottom margin, but I can’t guarantee that there won’t be any text after it, because I’m dealing with making a custom post style for my posts on forum using a horrible frontend that still uses tables for layout in 2018).

fantasai commented 5 years ago

As the main use case for this is controlling margins, we've added a 'margin-trim' property, see discussion in https://lists.w3.org/Archives/Public/www-style/2018Nov/0005.html

Are there other major use cases for this?

ExE-Boss commented 5 years ago

There are the same use cases as for :first‑child and :last‑child, but I don’t know how major those are.

simevidas commented 4 years ago

I don’t know how important my use-case is, but on my website, I style <strong> elements differently (small caps, extra bold) if they appear before any other text in a paragraph.

Screen Shot 2019-09-22 at 5 23 27 PM

Currently, I have to use JavaScript to add a CSS class to these elements, but I would use the :first-node selector instead if it was cross browser.

// add class to paragraphs that begin with a <strong> element
for (let p of article.querySelectorAll('p')) {
  let node = p.childNodes[0];
  if (node && node.nodeName === 'STRONG') {
    p.classList.add('Issue__note');
  }
}
oriadam commented 3 years ago

I have another use case: I style emojione images differently when they appear in the middle of text, than when they are solo. image

tabatkins commented 3 years ago

@emilio Can you elaborate on why these are slow? Presumably they're slow in comparison to :first-child? Is it related to the "ignore text nodes that are just whitespace" bit?

brandonmcconnell commented 3 years ago

I just needed this same selector for a project I was working on today. I would like to target a span tag that exists in a block of text. If it is not the first node, I would like to apply margin-left to space it away from the text to its left, and it is not the last node, I would like to apply margin-right to space it away from the text to its right. Unfortunately, this isn't currently possible in pure CSS, because one span tag inside a body of other text will match both :first-child and :last-child even though it is neither the first nor the last node.

This is true for all three examples below:

<!-- Example 1 -->
Lorem ipsum <span>TEST</span> lorem ipsum

<!-- Example 2 -->
<span>TEST</span> lorem ipsum lorem ipsum

<!-- Example 3 -->
Lorem ipsum lorem ipsum <span>TEST</span>
kizu commented 2 months ago

Another use case for this could be changing back the first abbreviation/number from oldstyle numerals and small caps when it is at the beginning of the paragraph. Currently, we don't have any way to know if some element is there. (motivated by the discussion from https://front-end.social/@kizu/112637529030687445, example CodePen: https://codepen.io/kizu/pen/JjqpKqK; ideally we'd want to actually target the first word/node in the sentence, but I don't know if we could ever achieve this with just CSS).

ggedde commented 1 month ago

For me, I am looking at styling these buttons differently:

<button><svg>...</svg> Click Here</button>
<button>Click Here <svg>...</svg></button>

But unfortunately there is no way to determine if the text is before or after the svg. So maybe we could then do: button:has( > svg:not(:last-node)) or button:has( > svg:not(first-node)) without it affecting: <button><svg>...</svg></button>