I have a shadow component in stenciljs like
#Component({
tag: "digital-verification",
styleUrl: "digital-verification.scss",
shadow: true,
})
and I have some css variable like
:host {
--app-primary-color: #488aff;
--app-border-radius: 10px;
--app-error-color: #EE320C;
}
Till now everything is working perfect and no issue.
Now I want to set this variables in the code like
document.querySelector(':root').style.setProperty('--app-primary-color', '#ffffff');
I tried
document.querySelector(':host').style
and
document.querySelector(':root').style
and
document.querySelector(':root').shadowRoot.style
and
document.querySelector(':root').root.style
and
#Element() el;
...
this.el.shadowRoot.querySelector(":host") // this is null
but I get compile error that Element doesn't have style
Wondering how should I see css variables in the code.
update
I did below code, and I can see value is set in console, but application color doesn't change
document.documentElement.style.setProperty("--app-primary-color", "#ff0000");
console.log(document.documentElement.style.getPropertyValue("--app-primary-color"));
In case somebody else has same issue
I did the below steps and issue is resolved
1- I deleted the below section
:host {
--app-primary-color: #488aff;
--app-border-radius: 10px;
--app-error-color: #EE320C;
}
2- defined a variable like below
$primary-color : var(--app-primary-color, #488aff);
3- in code set the css variable like below
document.documentElement.style.setProperty("--app-primary-color", "#ffff00" , "important");
Updated solution based on #Danny '365CSI' Engelman comment
1- You can delete the below section if you want to variable be applied to all children or keep it if you want it to be applied only on this component
:host {
--app-primary-color: #488aff;
--app-border-radius: 10px;
--app-error-color: #EE320C;
}
2- define #Element() el: HTMLElement; in your component
3- in code set the css variable like below
this.el.style.setProperty("--app-primary-color", "#ffff00");
Related
I have the following css which is loaded into my project:
// Default theme (light mode)
:root {
/* Typography */
--col-body-text: #0b0c0c;
--col-body-text-light: #505a5f;
}
// Dark mode theme
:root.dark {
/* Typography */
--col-body-text: #c5c5c5;
--col-body-text-light: #f8f8f8;
}
In my actual app this works as expected, however, in storybook, it ignores the dark mode variables.
I have updated my preview.js file to add '.dark' to the `HTML element when dark mode is selected - which works as expected - indeed all of the other dark mode specific code in the components works fine. It's only those variables that are being ignored.
Is there an issue with using :root in storybook that I'm not aware of or something?
if it helps, here is the code that adds the class to the HTML element:
// get an instance to the communication channel for the manager and preview
const channel = addons.getChannel()
// switch body class for story along with interface theme
channel.on('DARK_MODE', isDark => {
if (isDark) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
})
If you place such stylesheet in the HTML page (e.g. in the <head>), the :root selector refers to <html> (https://developer.mozilla.org/en-US/docs/Web/CSS/:root). If you want to use the :root selector with a class, you need to set the class on the <html> element (rather than on <body> suggested in another answer), so document.documentElement.classList.add('dark') is correct.
There is a playground which uses the Storybook syntax and features where I created a working example for you: https://webcomponents.dev/edit/p8TI3583HotsFNWjBMd8/src/index.stories.js
Not sure if your Storybook is configured in another manner, maybe your stylesheet is not really added or gets overwritten somewhere later. Please also verify if you use the CSS Custom Properties (aka CSS vars) correctly, I hope the working demo helps there too.
try
CSS -> body.dark {
instead of :root.dark {
and
channel.on('DARK_MODE', isDark => document.body.classList.toggle('dark', isDark))
I want to make an <input> text box where you write a certain color, say 'red' and a certain text gets colored like that. I found some guidelines on how to do it, but the code is in JavaScript, instead of TypeScript. So far I got this:
HTML
<input id="color" />
<h1>Change the color</h1>
CSS
<style>
h1 {
color: var(--color, blue)
}
</style>
JavaScript
const color = document.querySelector('#color');
color.addEventListener('input', e => {
document.documentElement.style.setProperty('--color', color.value)
})
As I am using .ts classes, I am wondering how can the JavaScript above be written instead?
To achieve that you should read the value of input (let's use two-way binding via [(ngModel)] directive), and then just use this value to apply as a style rule ([style.color] fits perfectly for this). And finally, you should end up with just a few lines of code:
HTML:
<input [(ngModel)]="color" />
<h1 [style.color]="color">Change the color</h1>
TS:
export class AppComponent {
color: string;
}
Here is a STACKBLITZ.
I also defined a default blue color in CSS just for example. This works as a default color because style rules defined via style attribute have a higher priority in this case.
UPDATE
If you want to control the color of all the elements over your app, you can use #HostBinding('style') at the top-level component this way:
export class AppComponent {
color: string;
#HostBinding('style')
get myStyle(): SafeStyle {
return this.sanitizer.bypassSecurityTrustStyle(`color: ${this.color};`);
}
constructor(private sanitizer:DomSanitizer) {}
}
Here is a STACKBLITZ.
In angular we've a directive called ngStyle, which can help you to do that.
So on your html you can add the code bellow:
HTML
<input [(ngModel)]="color" >
<div [ngStyle]="{'background': color}">...</div>
TS
color: string;
So any color you type inside your input, will be rendered, you can use the name of the color or it's code, like #000 for black or #fff for white and so on.
I'm trying to do a standard implementation of ref so that I can insert children elements into my InfoBox. But whatever I seem to put as a 'ref' element, never makes it to my InfoBox component. The result is always {} undefined from the log calls.
The click handler is to test timing issues, as using created vs mounted seemed to be a common issue.
<InfoBox
v-if="waitingForCode">
<p ref="infoboxcontent">A 7-digit verification code has been sent.</p>
</InfoBox>
and
<template>
<div
class="info-box"
#click="clicked" >
{{ this.$refs.infoboxcontent }}
</div>
</template>
<script>
export default {
name: 'InfoBox',
mounted() {
console.log(this.$refs, this.$refs.infoboxcontent)
},
methods: {
clicked() {
console.log(this.$refs, this.$refs.infoboxcontent)
}
}
}
</script>
<style scoped>
// some style
</style>
I'm starting to think I fundamentally misunderstand the usage of the 'ref' attribute since this seems like a trivial example. Any help would be greatly appreciated.
The ref Vue special attribute is used to refer a DOM node (or a child component) from your current component template.
If you want to pass some content to a custom component, this is the use case for a <slot> Vue built-in component.
During the upgrade process of Font Awesome 5 from 4.7.0, I noticed that any class bindings I gave to an <i> tag would not function as it did before.
Imagine the following element with a class binding:
<i class.bind="iconClass"></i>
And imagine the initial value of the iconClass being 'fas fa-cog'. When changing the value of iconClass to 'fas fa-ship', the icon won't update to the newly set icon classes. It will remain a cog icon.
I believe this happens because Font Awesome 5 replaces the <i> tags with <svg> tags and doesn't copy over the class binding properly and thus not trigger an icon change.
In the following example, the bound classes are changed after two seconds to illustrate the problem, please see this GistRun for an example of the issue. See app.html and app.js for the implementation. It also contains a dirty workaround.
How can/should this behaviour be implemented?
Inspired by the answer of #huocp utilising innerhtml.
Using a custom component to solve this problem does indeed work. However, as I will only be having one icon with a class binding in my project, I have found a simpler way of doing this:
<i innerhtml="<i class='${iconClass}'></i>"></i>
A change of the iconClass value will generate a new <i> child (while replacing the old <svg> element) within the parent, after which Font Awesome 5 will convert this <i> child into an <svg>.
Any elements can be used as parent and child elements of course, I just think <i> looks short and clean, it will nest the <svg> generated by Font Awesome 5 within the <i>.
See working gistrun here: https://gist.run/?id=55559f3bd606aa854502f3ddbbcad480
Use this custom component like this.
<fa-svg icon-class.bind="iconClass"></fa-svg>
fa-svg.js
import {inject, inlineView, bindable} from 'aurelia-framework';
#inject(Element)
#inlineView("<template></template>")
export class FaSvg {
#bindable iconClass;
constructor(element) {
this.element = element;
}
iconClassChanged(newIcon) {
if (!this.live) return;
this._rebuild(newIcon);
}
attached() {
this.live = true;
this._rebuild(this.iconClass);
}
detached() {
this.live = false;
}
_rebuild(iconClass) {
this.element.innerHTML = iconClass ? `<i class="${iconClass}"></i>` : '';
}
}
I had exactly same issue before.
As you said, you were using "svg with js" (the fa5 recommended way). It does fight Aurelia since the svg version use JavaScript to replace DOM element, the binding of Aurelia getting destroyed along with old DOM element.
Switch to "web font with css" which fa5 supported perfectly. It will obey your command as fa4.
I am searching a way to styling shadow DOM from the outside. For example, I would like to set the color of all text in all 'span.special' elements as RED. Including 'span.special' elements from shadow DOM. How I can do this?
Previously there were ::shadow pseudo-element and /deep/ combinator aka >>> for this purpose. So I could write something like
span.special, *::shadow span.special {
color: red
}
But now ::shadow, /deep/ and >>> are deprecated. So, what do we have as a replacement of them?
I did try many methods, including those described here. Since I'm using an external Web Component lib, I don't have access to modify these components. So, the only solution that worked for me was using JS querySelector, like this:
document.querySelector("the-element.with-shadow-dom")
.shadowRoot.querySelector(".some-selector").setAttribute("style", "color: black");
Not the best solution, not suitable for large stylings, but does work for little enchancements.
#John this was tested with Chrome 83.0.4103.116 (still going to test in Safari) and I did for Ionic (v5) ion-toast component. Here is the (almost) real code I used:
import { toastController } from '#ionic/core';
let toastOpts = {
message: "Some message goes here.",
cssClass: "toast-with-vertical-buttons",
buttons: [
{
text: "Button 1",
side: 'end'
},
{
text: "Button2",
side: 'end'
},
{
icon: "close",
side: "start"
}
]
}
toastController.create(toastOpts).then(async p => {
let toast = await p.present(); // this renders ion-toast component and returns HTMLIonToastElement
toast.shadowRoot.querySelector('div.toast-button-group-end').setAttribute("style", "flex-direction: column");
});
There is still no easy way to pierce through the shadow root, but here are 3 ways you can go about it. Just keep in mind that you will need to make changes inside the web component.
Using variables v1 - You will need to pass the property and consume the variable inside the web component.
Using variables v2 - You will need to consume the variable inside the web component.
Using ::part() - You will need to add a part attribute to the element you want to style in the web component. (Note: this pseudo element is well supported but is still in experimental mode, so make sure you're aware of that before using it in production).
Run code sample below for details.
const elA = document.querySelector('custom-container-a');
const shadowRootA = elA.attachShadow({mode:'open'});
shadowRootA.innerHTML = '<style>:host([border]) {display:block;border: var(--custom-border);}</style>'+
'<p>Shadow content A</p>'
const elB = document.querySelector('custom-container-b');
const shadowRootB = elB.attachShadow({mode:'open'});
shadowRootB.innerHTML = '<style>p {display:block;color: var(--custom-color, blue);}</style>'+
'<p>Shadow content B</p>'
const elC = document.querySelector('custom-container-c');
const shadowRootC = elC.attachShadow({mode:'open'});
shadowRootC.innerHTML = '<p part="paragraph">Shadow content C</p>'
/* Normal way of styling */
p {
color: orange;
}
/* Using variables version 1 */
custom-container-a {
--custom-border: 3px solid gold;
}
/* Using variables version 2 */
custom-container-b {
--custom-color: green;
}
/* Using ::part() */
custom-container-c::part(paragraph) {
color: magenta;
}
<p>Light content</p>
<custom-container-a border></custom-container-a>
<custom-container-b></custom-container-b>
<custom-container-c></custom-container-c>
You could use #import css as explained in this answer to another question on SO.
Include the rule inside the style element in the shadow tree.
<style>
#import url( '/css/external-styles.css' )
</style>
Note that the >>> combinator is still part of the CSS Scoping Module Draft.
Well, #import is not a solution if you are working with library web component that you can't change ...
Finally I found several ways to do it:
1) Cascading. Styles of Shadow DOM's host element affect Shadow DOM elements also. Not an option if you need to style a particular element of the Shadow DOM, not every.
2) Custom properties https://www.polymer-project.org/1.0/docs/devguide/styling
If an author of the web component provided such.
3) In Polymer, the have Custom Mixins also https://www.polymer-project.org/1.0/docs/devguide/styling
4) #import, but only for not-library components
So, there are several possibilities, but all of them are limited. No powerful enough way to outside styling as ::shadow were.