TinkerNG
TinkerNG

Reputation: 53

webcomponent style applied all in safari and firefox using webcomponentsjs

I have two custom element:

<name-card data-color="#000000"></name-card>
<name-card data-color="#80B88C"></name-card>

which I will applied their data-color to the style. From I asked question before, how to get cssRules, I got the answer and added it to attachedCallback() for dynamic changes.

But, it just shows the right colour on chrome, other browser applied the last colour which is #80B88C to both.

I checked the loop in attachedCallback()

for(i = 0; i < rules.length; i++){
    if(rules[i].selectorText === '.base'){
       rules[i].style.background = bgColor;
    }
}

and both changed the correct colour, but I still have no idea why they will applied the last colour to both elements style on safari and firefox.

Upvotes: 3

Views: 128

Answers (1)

Supersharp
Supersharp

Reputation: 31209

This is due to the fact the polyfill cannot realize totally the css isolation.

So the CSS stylesheet that you add in a polyfilled Shadow DOM is in fact applied to the whole document. When the same name rule is used twice, the last one wins. That's why you observe this behaviour in Safari and Firefox.

There's a workaround to this issue in your case, which deals with the ::before and ::after pseudo-elements:

Curent Solution

  • Generate a more specific cssRule which includes the specific color:

Inside the attachedCallback() function:

sheet.insertRule( '[data-color=' + bgColor + '] .base::before { background: ' + bgColor + ' ; }', sheet.length )

Future Solution

When the the CSS 3 specification for attr() on every properties will be implemented:

  • Propagate the color to the targeted elements and use the attr() CSS function to apply the right color, passed by attribute (data-color in your example).

The advantage of this solution is that it works the same way will polyfill and native implementation, and with an unlimited number or colors.

In the attachedCallback() function:

var targets = this.querySelectorAll( '.base' )
for ( var i = 0 ; i < targets.length ; i++ )
{
    targets[i].dataset.color = bgColor  
}

In the <style> element:

.base::before {
    background: attr( data-color )
}

var proto = Object.create( HTMLElement.prototype )
proto.createdCallback = function ()
{
  this.createShadowRoot()
  var tmpl = document.querySelector( 'template' )
  this.shadowRoot.appendChild( tmpl.content.cloneNode( true ) )
}
proto.attachedCallback = function ()
{
  //get custom color
  var bgColor = this.dataset.color

  //add it to the top-level inside Shadow DOM
  this.shadowRoot.querySelector( 'section' ).dataset.color = bgColor
						
  //change the stylesheet in the next microtask
  var style = this.shadowRoot.querySelector( 'style' )
  setTimeout( function () 
  {
    var sheet = style.sheet
    sheet.insertRule( '[data-color=' + bgColor 
           + '] .base::after { background: ' + bgColor + ' ; }'
           , sheet.cssRules.length )
  } )
}
document.registerElement( 'name-card', { prototype: proto } )
<name-card data-color="red"></name-card>
<name-card data-color="green"></name-card>
<template>
  <style>
    .base::after { 
      content: '-postfix' ;
    }
  </style>
  <section>
    <div>Custom Element</div>
    <span class="base">Base element</span>
  </section>
</template>	

Upvotes: 0

Related Questions