Apply CSS styles to Light DOM (shadow DOM) in custom element HTML5 - javascript

I have read all the documentation about web components and according to the standards it is not possible to apply isolated CSS styles (shadow) to the elements that the user enters inside a custom element (light DOM), that is, the content that the user adds within a slot element, an example below:
 
<! - Custom element ->
    <index-book>
     <slot>
       <! - Light DOM here / This content was introduced by the user ->
       <div class = "container">
         <span class = "section"> Section title ... </ span>
         <ul class = "sections">
           <li> ... </ li>
           <li> ... </ li>
           <li> ... </ li>
         </ ul>
       </ div>
     </ slot>
    </ index-book>
In fact, making use of the pseudo-element class of CSS ::slotted () could apply styles only to the first direct child of the slot element, that is, to div.container, but not to its children.
I have reached two conclusions, or if you can apply Shadow styles to the entire structure of elements of the DOM light and I do not know how, or the second option is that the user should not be allowed to enter content into a slot that has multi -level as in the previous example, div within div ...
If the correct answer is the second one, how should I do so that the user inserts content within the custom element and the final result is the same or similar to the example shown above (trying to create a custom book index element) and can apply isolated styles in the DOM tree of the custom element.
I must mention that I am not using Polymer or any other library to develop this custom element.
Thank you very much!

According to web fundamentals:
<name-badge>
<h2>Eric Bidelman</h2>
<span class="title">
Digital Jedi, <span class="company">Google</span>
</span>
</name-badge>
<style>
::slotted(h2) {
margin: 0;
font-weight: 300;
color: red;
}
::slotted(.title) {
color: orange;
}
/* DOESN'T WORK (can only select top-level nodes).
::slotted(.company),
::slotted(.title .company) {
text-transform: uppercase;
}
*/
</style>
<slot></slot>
So I guess you're out of luck here.
However if it's light dom maybe you could style it directly or wrap it in another custom element?

Why not use use normal CSS, that is bundled with your web component file, but applies to the normal dom instead of the shadowDOM, e.g.
<style>
index-book.container {
color: red;
}
index-book.sections{
color: blue;
}
/* or */
index-book > div {
color: red;
}
</style>
Since these styles beging with the custom web component name, they will not apply to any other elements

Related

:host-context not working as expected in Lit-Element web component

I've got two Lit-element web components - one is units-list, which contains many units-list-item elements. The units-list-item elements have two different display modes: compact and detailed. Because the list element supports infinite scroll (and thus could contain several thousand units), we need any mechanism that toggles between the two modes to be as performant as possible.
That's why I thought an ideal solution would be to use the :host-context() pseudo-selector in the styles for the units-list-item element, as that way every units-list-item element could switch between the two display modes just by changing the class applied to an ancestor (which would be within the shadow DOM of the units-list element).
To elaborate, here's the relevant markup from the units-list element. Note that the "trigger" classes are being applied to the #list-contents div, which is part of the units-list template.
<div id="list-contents" class="${showDetails ? 'detail-view table' : 'compact-view table'}">
${units.map(unit => html`<units-list-item .unit="${unit}"></units-list-item>`)}
</div>
As you can see, the showDetails flag controls whether the "detail-view" or "compact-view" class is applied to the div containing all of the units-list-item elements. Those classes are definitely being applied correctly.
Here's the full render method from the units-list-item element (unnecessary markup removed):
render() {
const {unit} = this;
// the style token below injects the processed stylesheet contents into the template
return html`
${style}
<div class="row compact">
<!-- compact row markup here -->
</div>
<div class="row detail">
<!-- detail row markup here -->
</div>
`;
}
Then I have the following in the units-list-item element's styles (we're using SCSS, so the single-line comments are not a problem):
// This SHOULD hide the compact version of the row when the
// unit list has a "detail" class applied
:host-context(.detail-view) div.row.compact {
display: none !important;
}
// This SHOULD hide the detail version of the row when the
// unit list has a "compact" class applied
:host-context(.compact-view) div.row.detail {
display: none !important;
}
My understanding of the :host-context selector says that this should work, but Chrome just renders both versions of the row every time, and the Chrome dev tools show that the selectors are never matching with either of the rows.
I know there are several alternatives that would work, but this is the only one I'm aware of that would allow the entire list of units to switch modes by changing a single class on a parent element. Every other solution I've considered would require, at the least, updating the class attribute on every units-list-item element in the list. I'd like to avoid that if possible.
Of course, my primary concern is simply to make this work, if possible, but I'm also curious about a couple of things and can't find any info about them. The two questions I can't seem to find an answer for are
When :host-context is used within an element that is itself part of a shadow DOM, does it consider that parent element's shadow DOM to be the "host context", or does it jump "all the way out" to the document DOM?
If it's the former, will :host-context jump multiple shadow DOM boundaries? Say I have a custom page element that contains a custom list element, which itself contains many custom item elements. If that item element has a :host-context rule, will the browser first scan up the shadow DOM of the list element, then, if matching nothing, scan up the shadow DOM of the page element, and if still matching nothing, then scan up the main document DOM to the <html> tag?
There is no support for :host-context in FireFox or Safari
last update from a month ago is both Mozilla and Apple are not going to implement it.
Looks like it is going to be removed from the spec:
https://github.com/w3c/csswg-drafts/issues/1914
One alternative is to use CSS Properties (those trickle down into
shadowDOM)
JSFiddle: https://jsfiddle.net/WebComponents/hpd6yvxt/
using host-context for Chrome and Edge
using CSS properties for other Browsers
Update Feb 2022
Apple quietly changed their mind? now in Safari TP:
https://caniuse.com/?search=host-context
An example of using css porperties, as Danny Engelman says, to get your goal
customElements.define('list-item', class extends HTMLElement {
constructor() {
const style = document.createElement('style');
const divcompact = document.createElement('div');
divcompact.innerHTML = "compact";
divcompact.className = "compact";
const divdetail = document.createElement('div');
divdetail.innerHTML = "detail";
divdetail.className = "detail";
let shadow = super().attachShadow({
mode: 'open'
});
shadow.append(style, divcompact, divdetail);
style.innerHTML = `
.compact {
background-color: red;
display: var(--display-compact, block);
}
.detail {
background-color: green;
display: var(--display-detail, block);
}
`
}
});
.compact-view {
--display-detail: none;
}
.detail-view {
--display-compact: none;
}
.box {
width: 200px;
height: 50px;
border: solid 1px black;
margin: 5px;
}
<div class="box">
no class applied
<list-item>test</list-item>
</div>
<div class="compact-view box">
compact view
<list-item>test</list-item>
</div>
<div class="detail-view box">
detail view
<list-item>test</list-item>
</div>

How default view encapsulation works in Angular

As we know, default view encapsulation for a component in angular application is Emulated,ie
encapsulation: ViewEncapsulation.Emulated
I really do not understand how it works behind the hood if it is not a shadow dom.
There are three types of encapsulation in angular
ViewEncapsulation.Emulated and this is set by default
ViewEncapsulation.None
ViewEncapsulation.Native
Emulated mode
Assume that you have two different components comp-first and comp-second , For example you define in both of them
<p> Some paragraph </p>
So if you apply some styling for paragraph in comp-first.css
p {
color: blue;
}
and then inspect p element on comp-first.html and look for its styling will find something like this
p[_ngcontent-ejo-1] {
color: blue;
}
"_ngcontent-ejo-1" is just a simple key for differentiate such an element from others components elements
None mode
If you apply this mode to such a component for instance comp-first and then you go and inspect any element it will not provide any attribute like "_ngcontent-ejo-1" to any element , So applying any styling or class it will be provided globally .
Native mode
This should give the same result as if you are using emulated mode but it comes with Shadow DOM technology in browsers which support it
When you write
<div class="XXX"></div>
With the style
XXX { color: red; }
The compiler will translate it to
<div class="XXX" ng_host_c22></div>
With the style
XXX[ng_host_c22] { color: red; }
it simply adds an unique (randomly generated) attribute to your elements and style, to avoid them colluding with other styles.
This means if you declare the class XXX in 2 different components, then they will have a different attribute, and not collude.

How to constrain the scope of inline CSS?

I'm rendering dynamic CSS for each item in a list. Each item will have potentially unique CSS rules for its elements, i.e.
<div id="thing1" class="vegas">
<style>
p {
font-size: 14pt; // this stuff is generated dynamically and i have no control over it
color: green;
}
</style>
<p>
I'm thing 1!
</p>
</div>
<div id="thing2" class="vegas" >
<style>
p {
font-size: 9pt; // so is this stuff
color: red;
}
</style>
<p>
I'm thing 2!
</p>
</div>
Is there a way to wrap each item in a general CSS rule that would limit the scope of each item's associated CSS to the item itself? Like
.vegas > * {
whatever-happens-in-here: stays in here;
}
Or any other way to handle scoping who-knows-what kinds of dynamically particular CSS?
The cascading style sheets are able to handle styling children of particular elements, so:
div#thing1 p {
rule: value; // Only applies to p in div with id="thing1"
}
div#thing2 p {
rule: value; // Only applies to p in div with id="thing2"
}
You need to know about the global styles that browsers have. For eg., find the below list:
font-family
line-height
text-align
The above have their default value as inherit, which means they get the property values from their parent, no matter what. So if you change the parent's property, your child also gets changed.
And you have other properties like:
margin
padding
border
width
height
These do not change, or inherit from the parent. So, if you wanna do something like what you wanted, you need to give your descendants, or immediate children, not to inherit or reset the styles for the children.
Why don't you use inline style attribute?
<p style="color:red;align:center"> Hello </p>
The above CSS style will only be applied to that particular paragraph tag.
You could use inline style statement for other tags and HTML elements too.
Or you could include an external common stylesheet and use the inline statements where you need a variation.CSS applies the latest style description it comes across.So the inline statements would over-ride the common css stylesheet effects.

Remove CSS style rules from a single HTML element programatically

I want to add some HTML elements in my document that has no style at all. But I need to assure that these elements will not look differently regardless of project, webpage or anything else really. These elements will be inserted in the page by Javascript and will be SPAN.
My idea is to add SPANs to style snippets of text in the document. But some style might have been added to SPAN elements before and that will change the result I am expecting.
So let's assume I'm writing a Widget and any of you could be using it in your own webpages. This is why I can't do much to change the elements' style directly, like changing the stylesheets directly. The solution must be achieved by Javascript. JQuery is not wanted.
<head>
<style>
span{
font-weight: bold;
/*anything else goes below*/
}
</style>
</head>
<body>
<span class='a_regular_span'>This text must be bold and anything else</span>
<span>This text must have only the CSS rules I applied by Javascript, and must not inherit the rules for all SPANs in the page</span>
</body>
Any ideas?
So let's assume I'm writing a Widget and any of you could be using it
in your own webpages. This is why I can't do much to change the
elements' style directly
You could use a style element with scoped attribute. This way you can style only your elements, without affecting other parts of the page.
But be aware that old browsers don't support it.
And if you don't want page's styles to affect your elements, see How can I prevent CSS from affecting certain element?
If you really wish to separate the style of your elements from that of the other elements on the page, you could use a custom tag to do this.
For example, instead of using span, you could use customspan and style those elements any way you like.
<head>
<style>
span{
font-weight: bold;
/*anything else goes below*/
}
</style>
</head>
<body>
<span class='a_regular_span'>This text must be bold and anything else</span>
<customspan>This text must have only the CSS rules I applied by Javascript, and must not inherit the rules for all SPANs in the page</customspan>
</body>
can u try this?
<style>
span{
font-weight: bold;
/*anything else goes below*/
} .a_regular_span span{ font-weight: normal;
/*anything else goes below*/
}
</style>

conflict between the same class or id of multiple css files

Is there any way to stop the conflict between same class or id of multiple css files. As I am explaining below for better understanding:
There is a master web page which has several <div> but there is a <div class"dynamic"> which always reload the contents including css files. Let's suppose if any class of master page has the same name to reloaded elements' class while properties are different. Then how should I handle this to stop the conflict.
master.html
<html>
<head> //attached master.css file here </head>
<body>
<div class="myClass"> </div>
<div class="dynamic"> /* often reload elements by ajax */ </div>
</body>
</html>
master.css
.myClass { height: 100px; width: 150px; background : red;}
.dynamic { height: 200p; width: 200px; }
now i am showing the reloaded html elements & css files into dynamic div of master page
reloaded tag line by ajax : <div class"myClass"> </div>
reload.css
.myClass{height: 30px; width: 25px; background: yellow; }
Now as you can see there are two classes with same name but different properties. Then how should I stop the confliction?
#Edit Thanks everyone for your support & time but my problem is different here.
the dynamic reloaded contents & css files are streaming from the client/user machine while master html page & it's css streaing directly from server.
so whatever the contents loads in dynamic div, it's coming from client side (e.g. tag lines & css, js). in that case i am not able to handle the css file which is just reloaded by ajax() so i think it can be sort out using js/jQuery fn().
You could apply the cascading rules of the CSS:
In your case, div.myClass inside div.dynamic should override div.myClass belongs to the body.
you adjust the reload.css rules to
.dynamic .myClass{height: 30px; width: 25px; background: yellow; }
The cascading rules which are applied when determine which rules should apply to html div could be referenced here
Updated 11.23
As the OP only have control over master.css, the above solution won't work. Thus, I suggest use child selector to limit the CSS rules to only the outer div.myClass. Modify the rule in your master.css to:
body > .myClass {...}
This rule will only apply to the .myClass which is the child of body. It leaves the spaces of styling for inner .myClass div.
Option 1: A more specific selector
.dynamic .myClass { }
This selector selects the .myClass element that is a descendent of .dynamic.
.dynamic > .myClass { }
This selector selects the .myClass element that is a direct child of .dynamic.
Option 2: Inline CSS
<div class="dynamic">
<div class="myClass" style="background-color: yellow;"></div>
</div>
Option 3: Use a different class.
UPDATE
If you want to avoid the previous defined property to be overwritten by a later defined value, you can use the !important syntax.
.myClass { background-color: red !important; } /* Sets the property to red */
.myClass { background-color: yellow; } /* Property is NOT overwritten */
If I understand your question correctly, this should sort it.
So you should add !important to the properties that seem to be overwritten.
div.myclass { ble ble }
div.main div.myclass { ble ble }
<body>
<div class="myclass"></div>
<div class="main><div class="myclass"></div></div>
</body>
Whichever css class of the same name is loaded last will overwrite anything set by the earlier class. However, if you use an inline style attribute this will always take precedence over anything set by the css file (so using an inline style is one option).
You could also use different style names or clarify your style with tag names div.myClass or id's #myDiv.myClass.

Categories

Resources