Global CSS effect on Shadow element. Why? - web-component

Both CSS (one in light dom and one in shadown dom) effect on tag.
Name and Phone are effected by global CSS at and Shadow Scope CSS too!!!
WHY ????? I dont want it.
I expected they are just effeted by Sahdow scope CSS which is in template scope.
I wish I have some ideas from you.
https://plnkr.co/edit/Asr1S1UFvhmhtZeWm5k8
//CREATE CUSTOM ELEMENT
var MyContactProtype = Object.create(HTMLElement.prototype);
MyContactProtype.createdCallback = function() {
//retrieve template
var tpl = document.querySelector('#contact-form-tpl');
var tpl_ct = document.importNode(tpl.content, true);
//this <=> my-contact element -> create shadow
var shadowRoot = this.createShadowRoot();
//show template in shadow DOM
shadowRoot.appendChild(tpl_ct);
};
//REGISTER CUSTOM ELEMENT
document.registerElement("my-contact", {
prototype: MyContactProtype
});
span {/* global CSS*/
text-decoration: line-through
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/document-register-element/1.11.0/document-register-element.js"></script>
<span>HELLO</span>
<!--line through by css at LINE 6: OK-->
<my-contact>
<h1 class="header-contact-form">Contact X</h1>
<span class="name">this is my name</span>
<span class="phone">this is my phone</span>
</my-contact>
<template id="contact-form-tpl">
<style>
span {/* shadow scope CSS*/
text-decoration: underline
}
</style>
<fieldset>
<legend>
<content select="h1.header-contact-form"></content>
<!--
Name and Phone are effected by CSS at line 6 and 21 too!!!
WHY ????? I dont want it.
I expected they are just effeted by CSS line 21 which is in template scope.
-->
<div>
Name: <span><content select="span.name"></content></span>
</div>
<div>
Phone: <content select="span.phone"><span></span></content>
</div>
<span>TEST</span><!-- only apply css at line 21: OK-->
</legend>
</fieldset>
</template>

It's the normal behavior of CSS styles. The elements inserted in the Shadow DOM with <content> (in your example: <span class=name>...</span>) are actually part of the normal DOM, and therefore are affected by the global CSS styles.
If you don't dont want it, you should try another technique, like copying the light DOM elements into the Shadow DOM instead of using <content>.
Also, you should use Custom Elements v1 (and customElements.define()) and Shadow DOM v1 (and <slot>) instead of Custom Elements v0 (registerElement()) and Shadow DOM v0 (<content>) which are deprecated.
With Shadow DOM v1, you can use ::slotted() inside the Shadow DOM to select and style insterted elements.
You can then overload the CSS rule in the Shadow DOM <style> with the !important modifier:
line 21:
<style>
::slotted( span ) {
text-decoration: underline!important
}
</style>
Below is the complete snippet:
//CREATE CUSTOM ELEMENT
class MyContact extends HTMLElement {
constructor() {
super()
//retrieve template
var tpl = document.querySelector('#contact-form-tpl');
var tpl_ct = document.importNode(tpl.content, true);
//this <=> my-contact element -> create shadow
var shadowRoot = this.attachShadow( {mode:'open'}) //createShadowRoot();
//show template in shadow DOM
shadowRoot.appendChild(tpl_ct);
}
}
//REGISTER CUSTOM ELEMENT
customElements.define("my-contact", MyContact);
span {
text-decoration: line-through
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/document-register-element/1.11.0/document-register-element.js"></script>
<span>HELLO</span>
<!--line through by css at LINE 6: OK-->
<my-contact>
<h1 slot=header>Contact X</h1>
<span slot=name>this is my name</span>
<span slot=phone>this is my phone</span>
</my-contact>
<template id="contact-form-tpl">
<style>
span ::slotted( span ),
span {
text-decoration:underline!important
}
</style>
<fieldset>
<legend>
<slot name=header></slot>
<div>
Name: <span><slot name="name"></slot></span>
</div>
<div>
Phone: <slot name="phone"><span></span></slot>
</div>
<span>TEST</span><!-- only apply css at line 21: OK-->
</legend>
</fieldset>
</template>

Related

set body of web component from attribuate

I've built a web component and I need to set the body of the component when I construct it.
My web component is:
import {html, PolymerElement} from '#polymer/polymer/polymer-element.js';
class TextBlock extends PolymerElement {
static get template() {
var body;
return html`
{body}
`;
}
}
window.customElements.define('text-block', TextBlock)
The page that contains the component is dynamically generated. What I want to do is insert a text-block element into the page as:
<body>
<text-block>
<H1>Title of TextBlock</H1>
</text-block>
</body>
My problem is that I don't understand how to get the web component to take the content between the text-block start/end tags and return it from the call to template()
Whilst not shown, I'm using a webcomponent as I need to style the text using shadow dom.
Don't use Polymer for new Web Components; Google deprecated it years ago.
See: https://polymer-library.polymer-project.org/
Google can't even keep that page up to date; they renamed Lit-Element/html to just Lit.
See: https://lit.dev
Or its 60+ alternatives: https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/
You can write Vanilla JavaScript Web Components without the soup-starter Lit
Here is a more extended example to cover your next questions:
<style> /* GLOBAL CSS */
text-block { /* inheritable styles trickle down into shadowDOM */
font: 20px Arial;
color: green;
}
div[slot] { /* lightDOM styles are REFLECTED to <slot> in shadowDOM */
width: 200px;
border: 2px solid green;
}
</style>
<text-block prefix="Hello!">
<!-- lightDOM, hidden when shadowDOM is used, then REFLECTED to <slot> -->
<div slot="username">Brett</div>
</text-block>
<text-block prefix="Hi,">
<!-- lightDOM, hidden when shadowDOM is used, then REFLECTED to <slot> -->
<div slot="username">Danny</div>
</text-block>
<template id="TEXT-BLOCK">
<style> /* styling shadowDOM */
div { /* styles do NOT style content inside <slot>, as <slot> are REFLECTED content */
padding: 1em;
background:gold;
}
::slotted(*) { /* slotted can only style the lightDOM 'skin' */
background: lightgreen;
}
</style>
<div>
<slot name="username"></slot>
</div>
</template>
<script>
customElements.define('text-block', class extends HTMLElement {
constructor() {
// see mum! You can use code *before* super() The documentation is wrong
let colors = ["blue","red"];
let template = id => document.getElementById(id).content.cloneNode(true);
super().attachShadow({mode: 'open'})
.append(template(this.nodeName)); // get template TEXT-BLOCK
this.onclick = (evt) => this.style.color = (colors=colors.reverse())[0];
}
connectedCallback(){
this.shadowRoot.querySelector("div").prepend( this.getAttribute("prefix") );
}
});
</script>
For (very) long answer on slots see: ::slotted CSS selector for nested children in shadowDOM slot

Combinators for multiple ::slotted elements

I am interested if there's a way to achieve something like the following:
::slotted(input[type="checkbox"]:disabled) ~ ::slotted(label) {
cursor: not-allowed;
}
By testing it on some of the examples, it does not work.
Specification does not describe if this should be possible or not. MDN does not cover this case as well.
I do not want to enclose neither input nor label inside the shadow-dom as I do not want to handle and/or duplicate the native behavior of those elements.
P.S. I know that I can do that with javascript (for example, by adding class to slotted label), but I'm looking for a plain css solution.
Full example:
<script>
customElements.define('my-element', class extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
::slotted(input:disabled) ~ ::slotted(label) {
color: red;
}
::slotted(input:disabled) + ::slotted(label) {
color: red;
}
</style>
<slot name="inputel"></slot>
<slot name="inputlabel"></slot>`;
}
});
</script>
<my-element>
<input disabled id="input1" type="text" slot="inputel"/>
<label for="input1" slot="inputlabel">label</label>
</my-element>
The full detailed explanation is at: ::slotted CSS selector for nested children in shadowDOM slot
<my-element>
<input disabled id="input1" type="text" slot="inputel"/>
<label for="input1" slot="inputlabel">label</label>
</my-element>
ShadowDOM SLOTs turn off the light
The input and label are in lightDOM,
and remain there invisible when reflected (not moved!) to shadowDOM SLOTs
So you have to style them in lightDOM:
<style>
/* style lightDOM!! */
my-element input:disabled ~ label {
color: red;
}
</style>
<template id="MY-ELEMENT">
<style>
::slotted(input:disabled){
border:1px dashed red;
}
</style>
INPUT <slot name="inputElement"></slot> <slot name="inputLabel"></slot>
</template>
<script>
customElements.define('my-element', class extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'})
.append(document.getElementById(this.nodeName).content.cloneNode(true));
}
});
</script>
<style>
/* style lightDOM!! */
my-element input:disabled ~ label {
color: red;
font-weight: bold;
}
</style>
<my-element>
<input disabled id="inp1" placeholder="This is disabled" type="text" slot="inputElement"/>
<label for="inp1" slot="inputLabel">input label 1</label>
</my-element>
<br>
<my-element>
<input id="inp2" placeholder="Not disabled" type="text" slot="inputElement"/>
<label for="inp2" slot="inputLabel">input label 2</label>
</my-element>
This use case is a bit contrived, there is nothing gained with my-element, user still has to declare input and label
Maybe create a <input-element type="text" label="A Label"> to create above HTML, you then have the disabled CSS in its shadowDOM (no need for SLOTs)
or (pseudo code)
<template id="INPUT-ELEMENT">
<style>
input:disabled~label{
color:red;
}
</style>
<input id="i"/>
<label for="i"><slot name="label"></slot></label>
</template>
...
connectedCallback(){
let inp=this.shadowRoot.querySelector("input");
inp.type = this.getAttribute("type");
inp.toggleAttribute( "disabled" , this.hasAttribute("disabled"));
}
<input-element type="text" disabled>
<span slot="label"><b>Fancy Label<b></slot>
</input-element>
if you want to give the user more control
::slotted came got into WebComponents V1 spec after its predecessor caused performance issues in V0
Thus it only takes simple selectors as parameter, and can only style first-level elements in the slot
So in above example ::slotted can style the <span slot="label">, but not the <b> inside.
Note: SLOTs are LIVE connections, you can re-apply CSS at any time, and content changes are immediately reflected to shadowDOM:
document.querySelector("input-element > b").innerHTML="Better Label!";
More SLOT related answers can be found with StackOverflow Search: Custom Elements SLOTs

Unknown prop `float` on <div> tag

I'm creating a component and I want to pass a style attribute via props to an internal div, but I get this error when trying to assign float:left
Unknown prop float on tag. Remove this prop from the element.
My component:
<Badge style={{width:'10em',float:'left'}}
color={props.user.color}
initials={props.user.initials}
name={props.user.name} />
the component code:
render() {
const {color,initials, name, style} = this.props;
return (
<div {...style}>
<div className="badge-wrapper">
<div style={{'backgroundColor': color}} className="badge">
{
initials.toUpperCase()
}
</div>
</div>
{name}
</div>
);
}
I don't see the issue with setting up a float left on a simple div, any recommendations?
Change
<div {...style}>
to
<div style={style}>
You are using JSX Spread attributes to apply your css properties to the div as attributes when all you really wanted to do was assign them to the style prop.

Change the parent style when a specific child is focused

I would like to change the border color of tags when the input is focused.
<div class="tags">
<input type="text">
</div>
using JavaScript
<div class="tags" id="tags">
<input type="text" id="text"/>
</div>
JavaScript
var parent = document.getElementById('tags');
var child = document.getElementById('text')
child.onfocus = function(){
parent.style.border="1px solid #f00";
}
child.onblur = function(){
parent.style.border="none";
}
JSFIDDLE
Using jQuery:
$('input[type="text"]').focus(function(){
$(this).parent().is('.tags').css({"border-color":"#F00"});
});
If you wanted to uses classes or tags instead of unique IDs you must consider how you would specifically target the element associated with the focused element.
This example only targets the the "#tags / .tags / tags" element that is a parent of the focused input.
This snippet a flexible way to achieve what it is you're trying to do.
You can give an id to your parent element and another id to your input tag. Then do the following ->
HTML
<div class="tags" id='parent'>
<input type="text" id='input'>
</div>
If you want to listen for any changes related to your input element you can use one of the DOM's native methods .addEventListener ->
Javascript
//getting our input element
var input = document.getElementById('input');
//when the element receives focus
input.addEventListener('focus', function(){
//getting our input element's parent element
parent = input.parentNode;
//then do the following
parent.style.border = '1px solid brown';
},false);
//when the element loses focus
input.addEventListener('blur', function(){
//getting our input element's parent element
parent = input.parentNode;
//then do the following
parent.style.border = '1px solid green';
},false);
CSS
#parent{
width:102px;
height:52px;
border:1px solid green
}
#input{
width:100px;
height:50px;
border:0px solid
}
DEMO

Dart polymer styling shadow root element with css

I'm trying to style the root element of a polymer element with css. Take the counter-click for example. If I have a polymer element in my main html and I want to give it a size (say width and height.) I try to use a global css in my main html like
click-counter{
width:100px;
height:100px;
}
This doesn't work.
I also tried to style inside the polymer element like
<polymer-element name="click-counter" attributes="count">
<template>
<style>
#host{
click-counter {
width:100px;
height:100px;
}
}
...
This doesn't work either. Does anyone know how am I gonna style the element with css?
(I'm using dart 0.8.1.2)
Thanks,
Yi
The click-counter is composed of a button and a text input. There is no div that holds the 2 together. So if you are setting the width and height on the component, what are you really setting it on?
<polymer-element name="click-counter">
<template>
<button on-click="increment">Click Me</button>
<p>You clicked the button {{count}} times.</p>
</template>
<script type="application/dart" src="click_counter.dart"></script>
</polymer-element>
In case there would be a div wrapping the button and the text input, you could set the width of the div in the component like this:
<polymer-element name="click-counter">
<template>
<style type="text/css">
div{
width:100px;
border-style:solid;
background-color: #FF9900;
font-weight: bold;
}
</style>
<div>
<button on-click="increment">Click Me</button>
<p>You clicked the button {{count}} times.</p>
</div>
</template>
<script type="application/dart" src="click_counter.dart"></script>
</polymer-element>
You can also declare a div style on the index file, but then you have to set applyAuthorStyles to true on the component.
#CustomTag('click-counter')
class ClickCounterElement extends PolymerElement with ObservableMixin {
#observable int count = 0;
get applyAuthorStyles => true;
void increment(Event e, var detail, Node target) {
count += 1;
}
}
This would affect all divs on the page though. I'm not sure how you would select only the root div in the click-counter component.
Is this what you want to do?
#host {
:scope {
...
}
}
See this section of the tutorial: Styling a custom element

Resources