Styling an extended native element in ShadowDOM - web-component

I understand that in order to style elements from the ShadowDOM, the shadowDOM itself has to "know"
the element, thus be declared inside of it.
It works well for regular web components but i haven't found an answer to it weather it's the same for an extended
native element.
For example, I wanted to know if the code I wrote is the best way, i.e. the external p acting as container, or am i creating a redundant p element.
JavaScript:
class P_example extends HTMLParagraphElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
p{
background-color: orangered;
width: max-content;
}
</style>
<p><slot>Some default</slot></p>
`;
this.shadowRoot.append();
}
connectedCallback() {
//add styling class to the p element
}
}
customElements.define("omer-p", P_example, { extends: "p" });
HTML:
<p is="omer-p">Some sample text</p>

Related

Is there a way to keep linked stylesheets localized to single components?

I have a component that relies on external stylesheets. I'm bringing the stylesheet into the component like this:
Child component
export default class Child extends Component {
render() {
return (
<div>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" />
...my code here...
</div>
);
}
}
But what's happening is this is forcing those styles onto the parent component as well.
Parent Component
export default class Parent extends Component {
render() {
return (
<div>
...code here...
<Child />
... more code here...
</div>
);
}
}
Is anyone aware of a way that I can keep that stylesheet link localized to just that child component so the styles aren't applied to the parent component as well?
Edit 2
Currently trying the shadow dom route, trying to pass down some children. Getting an error after the initial render saying Cannot read properties of undefined (reading 'children'). It does render the this.props.children initially...
import React, { Component } from 'react';
class MyComponent extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
${this.props.children}
`;
}
};
export default class Child extends Component {
render() {
return (
<div>
<script>
{!customElements.get("my-component") && customElements.define('my-component', MyComponent)}
</script>
<my-component>
<h1>Hello from shadow</h1>
</my-component>
<h1>Hello</h1>
</div>
);
}
}
You can try CSS Modules. Add :local(.className) to the class you want to use in your code which is in the font-awesome-min.css file. Then import the styles to your component. For example import styles from './font-awesome-min.css' then use the module in your code. The styles will only apply to specific element and won't affect other elements in the document. So let's say you have a class called .usericon in your css you do this in the css file.
CSS
:local(.usericon){
fill: red;
}
React Code
import styles from './font-awesome-min.css'
export default function Profile(){
return (
<i className={styles.usericon}>User Icon</i>
)
}
One way to truly isolate your CSS is with Web Components. Web Components are a browser API that allows defining custom elements with their own "shadow DOM". If a style is defined inside the shadow DOM, it is truly sandboxed with no styles going in or out. You can use whatever selectors you like:
class FancyBox extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
.fancy-box {
border: solid 3px darkblue;
background: dodgerblue;
padding: 10px;
color: white;
font: 16px sans-serif;
}
</style>
<div class="fancy-box">
<slot></slot>
</div>
`;
}
}
customElements.define('fancy-box', FancyBox);
.fancy-box {
border: dashed 3px darkred !important;
background: crimson !important;
padding: 10px !important;
color: white !important;
font: 16px sans-serif;
}
<fancy-box>Safe in my shadow DOM</fancy-box>
<div class="fancy-box">I am affected by outside stylesheets</div>
Note the use of <slot></slot>. This is a placeholder for child elements of the component.
If I wanted to use this custom element from React, it needs to be defined separately so it only runs once.
class FancyBox extends HTMLElement { /*...*/ };
customElements.define('fancy-box', FancyBox);
class ReactFancyBox extends React.Component {
constructor() {
super();
this.state = { value: 'hello world!' }
}
handleChange(e) {
this.setState({ value: e.currentTarget.value });
}
render() {
return (
<div>
<fancy-box>
<strong>{this.state.value}</strong>
</fancy-box>
<input value={this.state.value} onChange={e => this.handleChange(e)} />
</div>
);
}
};

CSS Modules - exclude class from being transformed

I'm using CSS modules and by far everything was working great.
We started to use external UI library along with our own one, so I'm writing components like this:
<div className={styles['my-component']}>
<ExternalUIComponent />
</div>
Assuming that the ExternalUIComponent has its own class that in the final CSS file looks like this external-ui-component, how can I make adjust this component styling from my css file? The below example does not work:
.my-component {
font-size: 1em;
}
.my-component .external-ui-component {
padding: 16px;
// Some other styling adjustments here
}
Please do not use inline styles as someone else suggested. Stay away from inline styles as much as you can because they can cause unnecessary re-renders.
You should use global instead.
.my-component {
:global {
.external-ui-component {
padding: 16px;
// Some other styling adjustments here
}
}
}
https://github.com/css-modules/css-modules#usage-with-preprocessors
Also, I recommend using camel case style names which is the preferred way for css-modules.
So your class name would be : .myComponent { ... }
And you can use it in your code as
<div className={ styles.myComponent } >
If you wanted to add more styles , you can use the array.join(' ') syntax.
<div className={ [ styles.myComponent, styles.anotherStyle ].join(' ') } >
This is cleaner!
Here's a shorter form in pure CSS (i.e. no preprocessor needed):
.my-component :global .external-ui-component {
// ...
}
Did you try inline styles for that component ?
https://reactjs.org/docs/dom-elements.html#style
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',
};
function HelloWorldComponent() {
return <div style={divStyle}>Hello World!</div>;
}

Why does my Web Component CSS not show? I am not using shadowDOM

I have a Native V1 component that is not using shadowDOM so I place my CSS in the <head>. But when someone else uses my component my CSS no longer works.
This only happens if their component does use shadowDOM.
Example Code for my component:
class MyEl extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = `<div class="spaced"><button class="happy-btn">I'm Happy</button></div>
<div class="spaced"><button class="sad-btn">I'm Sad</button></div>`;
}
}
// Define our web component
customElements.define('my-el', MyEl);
button {
padding: 8px 20px;
}
.happy-btn {
background-color: pink;
}
.sad-btn {
background-color: #007;
color: white;
}
<my-el></my-el>
My CSS is loaded into the <head> tag since I am not using shadowDOM. But once the outer element includes me in their shadowDOM then things fall apart.
If you are creating a component that does NOT use ShadowDOM that you may still need to add your CSS into a shadowRoot. If someone else places your component into their shadowDOM, then you must add your CSS to their shadowRoot. You can do this with the following code:
const myStyle = document.createElement('style');
myStyle.setAttribute('component', 'my-el');
myStyle.textContent = ` button {
padding: 8px 20px;
}
.happy-btn {
background-color: pink;
}
.sad-btn {
background-color: #007;
color: white;
}`;
function addCss(el, selector, styleEl) {
// Check to see if we have been placed into a shadow root.
// If we have then add our CSS into that shadow root.
let doc;
try {
doc = el.getRootNode();
if (doc === document) {
doc = document.head;
}
}
catch(_ex) { doc = document.head; } // Shadow DOM isn't supported.
if (!doc.querySelector(selector)) {
doc.appendChild(styleEl.cloneNode(true));
}
}
class MyEl extends HTMLElement {
constructor() {
super();
addCss(this, 'style[component="my-el"]', myStyle);
}
connectedCallback() {
this.innerHTML = `<div class="spaced"><button class="happy-btn">I'm Happy</button></div>
<div class="spaced"><button class="sad-btn">I'm Sad</button></div>`;
}
}
customElements.define('my-el', MyEl);
class TheirEl extends HTMLElement {
constructor() {
super();
this.attachShadow({mode:'open'});
this.shadowRoot.innerHTML = `<hr/><my-el></my-el><hr/><my-el></my-el><hr/>`;
}
}
customElements.define('their-el', TheirEl);
<their-el></their-el>
The function addCss will place your CSS into the correct shadowRoot, or into document.head if there is no shadowRoot.
You must call addCss within your constructor to place the CSS in the correct location. This routine will also make sure you don't add it twice as long as you have a unique selector to identify your <style> tag.
In mine you see the <style> tag adds an attribute called component with a value of the component name. In my case component="my-el".
Then I use the selector 'style[component="my-el"]' to see if that tag is already in the shadowRoot, or document.head if there is no shadowRoot, and only add the styles if it does not already exist.
You can not assume that your component will not be in shadow DOM just because you are not using it. Use the example above to protect yourself.
Side Note
If you are using shadow DOM then this problem goes away since your have to place your CSS into your own shadowRoot.

Styling a child of a shadow root in Shadow DOM

I'm trying to style a child element of a shadow DOM root.
This defines a custom element called element-el, which have a span class-named 'x' with the letter x in it, which I want, for the state of the matter, to be red.
class El extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow({mode:'open'});
shadow.innerHTML = '<span class="x">X</span>';
}
}
customElements.define ('element-el',El);
I've tried those CSS styles:
element-el::slotted(.x) {
color:red;
}
element-el::host .x {
color:red;
}
element-el:host .x {
color:red;
}
element-el::shadow .x {
color:red;
}
element-el /deep/ .x {
color: red;
}
element-el::content .x {
color:red;
}
The X does not become red.
I'm using Chrome 56, which is supposed to support this...
I want to style it without putting a style element inside the shadow DOM.
Here is a codepen:
http://codepen.io/anon/pen/OpRLVG?editors=1111
EDIT:
This article suggests that it is possible to style shadow children from an external CSS file -- are they simply wrong?
Apparently the problem is the fact that you are trying to use the global CSS to style the shadow tree elements.
You can use the :host pseudo selector, however in order to do so you will have to place the style inside the shadow tree content.
Make the following changes in your javascript:
class El extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow({mode:'open'});
var innerHTML = '';
innerHTML += '<style>';
innerHTML += ':host(element-el.red) span {color: red}';
innerHTML += ':host(element-el.green) span {color: green}';
innerHTML += ':host(element-el.blue) span {color: blue}';
innerHTML += '</style>';
innerHTML += '<span class="x">X</span>';
shadow.innerHTML = innerHTML;
}
}
customElements.define ('element-el',El);
Check a functional example in your updated codepen.
A simple solution is to define the x class in the Shadow DOM:
class El extends HTMLElement {
constructor() {
super()
this.attachShadow({mode:'open'})
.innerHTML = `
<style>
.x { color: red }
</style>
<span class="x">X</span>`
}
}
customElements.define ('element-el',El)
<element-el></element-el>
Note: Because of Shadow DOM's style encapsulation, you'll always need to put a <style> element in the Shadow DOM, whether using Romulo's :host solution, a direct class declaration (see above), or an external stylesheet.
Of course if you use an inherited CSS property (like color) that will aplly to all your shadow DOM content, you can just use normal CSS:
class El extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow({mode:'open'});
shadow.innerHTML = '<span class="x">X</span>';
}
}
customElements.define ('element-el',El);
element-el {
color: red;
}
<element-el></element-el>
This may help someone else in a similar situation.
In my case I have:
<svg class="icon">
<use xlink:href="#my-icon"></use>
</svg>
and to access the rendered icon in #shadow-root, I use:
.icon use {
fill: #f80;
}

passing data to the styles component angular2

How can I pass data from angular tag to the styles in the #Component?
Here is my component:
import { Component, Input } from '#angular/core';
#Component({
selector: 'icon',
template: `<svg class="icon"><use attr.xlink:href="#{{name}}"></use></svg>`,
styles: ['.icon{width:{{size}}px;}']
})
export class IconComponent {
#Input() name: string;
#Input() size: any;
constructor() { }
}
I wanna set size property from component.
used in html file:
<icon name="logo" size="37"></icon>
Binding in styles is not supported. You can use style binding like
template: `<svg class="icon" [style.width.px]="size"><use attr.xlink:href="#{{name}}"></use></svg>`,
I'm actually surprised that I finally found a somewhat solid solution to this using a ngx-css-variables.
My use case is that I have a 3rd-party library, which creates many child components within itself, as it draws charts.
I needed to set the linear gradient with a url(#<uuid>).
CSS Template
/deep/ngx-charts-line-chart {
display: flex;
/deep/ngx-charts-chart {
display: flex;
div.ngx-charts-outer {
display: flex;
svg {
.line {
stroke: var(--gradient);
stroke-width: 4px;
}
}
}
}
}
Component
import * as uuid from 'uuid/v4';
...
private _linearGradientId = uuid();
get uuid() {
return this._linearGradientId;
}
get gradientCss() {
return {
'--gradient': `url(#${this.uuid})`
}
}
...
HTML Template
...
<ngx-charts-line-chart
[css-vars]="gradientCss"
...
<ngx-charts-line-chart>
You still have to deep style the component since it's components are not in your template, but ngx-css-variables will inject a function into the style property, which seems hacky, but it works!
So now the stroke comes from that dynamic function at runtime. Super cool. I wish angular supported this natively.

Resources