Search code examples
cssgoogle-chromeweb-componentnative-web-component

Suspected Chrome bug: CSS `last-of-type` not updating on webcomponent


NOTE THe below question is flawed, please rather refer to: Suspected Chrome bug: CSS `last-of-type` not updating in webcomponent if apply certain styling

Below you will see a list of gray blocks, items 111 to 555. 2 Seconds after running another item will be created via JS and appended to the list. The NEW item will be styled as last-of-type, but the previous last-of-type will remain styled as if was last-of-type, but it no longer is!

My current fix is when appending a new item to the list, hide the old last item (555) in this case, then show it again, this is done by setting display: none then display: block. Unfortunately it results in a flicker on more complex web components. Is there any way to make last-of-type update with a flicker? Am I using last-of-type incorrectly? Is it a Chrome bug?

class Component extends HTMLElement {
    constructor() {
    super().attachShadow({mode:'open'});
    const template = document.getElementById("TEMPLATE");
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }
}
window.customElements.define('wc-foo', Component);

setTimeout( () => {
   const child = document.createElement('div')
   child.textContent = "new"
   child.classList.add('child')
   CONTAINER.appendChild(child)
}, 2000)
#CONTAINER .child:last-of-type {
   border: 1px solid red;
}
<template id="TEMPLATE">
  <style>
      :host {
        background: gray;
        display: block;
      }
  </style>
  <slot>
   WC-FOO
  </slot>
</template>


<div id="CONTAINER">
    <wc-foo class="child">111</wc-foo>
    <wc-foo class="child">222</wc-foo>
    <wc-foo class="child">333</wc-foo>
    <wc-foo class="child">444</wc-foo>
    <wc-foo class="child">555</wc-foo>
</div>

Opened this bug report in the meantime: https://bugs.chromium.org/p/chromium/issues/detail?id=1417953


Solution

  • This isn't a bug, it's working as designed (and expected). :last-of-type refers to the element-type, not the class-name, of an element; therefore the last wc-foo – regardless of its class-name – remains the :last-of-type even when it has an adjacent sibling with the same class-name; as the documentation says:

    ...The :last-of-type pseudo-class represents an element that is the last sibling of its type.

    Citation: https://www.w3.org/TR/selectors-3/#last-of-type-pseudo.

    To demonstrate, instead of a <div> element we can add a <wc-foo> element:

    class Component extends HTMLElement {
      constructor() {
        super().attachShadow({
          mode: 'open'
        });
        const template = document.getElementById("TEMPLATE");
        this.shadowRoot.appendChild(template.content.cloneNode(true));
      }
    }
    window.customElements.define('wc-foo', Component);
    
    setTimeout(() => {
      const child = document.createElement('wc-foo')
      child.textContent = "new"
      child.classList.add('child')
      CONTAINER.appendChild(child)
    }, 2000)
    #CONTAINER .child:last-of-type {
      border: 1px solid red;
    }
    <template id="TEMPLATE">
      <style>
        :host {
          background: gray;
          display: block;
        }
    
      </style>
      <slot>
        WC-FOO
      </slot>
    </template>
    
    
    <div id="CONTAINER">
      <wc-foo class="child">111</wc-foo>
      <wc-foo class="child">222</wc-foo>
      <wc-foo class="child">333</wc-foo>
      <wc-foo class="child">444</wc-foo>
      <wc-foo class="child">555</wc-foo>
    </div>

    JS Fiddle demo.

    References: