I am using Stripe Elements for a credit card checkout. The issue is, that I am not able (or I simply don't know how) to use my own CSS variables on this Stripe Element.
I need to use CSS variables for the sake of changing colors when the user changes the theme. Here is my current implementation:
Variable definitions (I'm using SASS)
.theme1
--color-1: red
--color-2: pink
// ...
.theme2
--color-1: blue
--color-2: lilec
// ...
.theme3
--color-1: orange
--color-2: yellow
// ...
// ...
The CSS variables are defined under the scope of a class, that is put to the body depending which theme is currently selected.
HTML (I am using Angular 6)
<div #stripe></div>
Typescript
#ViewChild('stripe') el: ElementRef;
card: any;
cardHandler = this.onChange.bind(this);
async onSubmit() { /* ... */ }
setupStripe(): void {
this.card = stripeElements.create('card', {
iconStyle: 'solid',
style: {
base: {
iconColor: 'var(--some-var)',
// using css variables here does not work
// ...
},
}
});
this.card.mount(this.el.nativeElement);
this.card.addEventListener('change', this.cardHandler);
}
destroyStripe(): void {
this.card.removeEventListener('change', this.cardHandler);
this.card.destroy();
}
ngAfterViewInit() {
this.setupStripe();
}
ngOnDestroy() {
this.destroyStripe();
}
onChange({ error }) { /* ... */ }
Styles (I am using SASS)
.StripeElement
background-color: var(--dy-bg-1)
// I don't have access to font colors etc here
color: var(--dy-txt-1) !important
// !important also does not work
P.S.: It's important for me, that the variables will change at runtime (which is the reason I'm using CSS variables.
The Stripe documentation says
Elements creates UI components for you that are hosted by Stripe
i.e. their input fields are in a different document, so don't have access to your custom CSS variables.
A 'good enough' solution might be to read the CSS Custom Property values in your setupStripe method, and pass the values over as plain strings:
// Note: document.body is just an example:
const styles = getComputedStyle(document.body);
this.card = stripeElements.create("card", {
iconStyle: "solid",
style: {
base: {
iconColor: styles.getPropertyValue("--some-var")
// ...etc
}
}
});
Related
I am using CSS variables for a feature where the user has an option to change the font-size to small, medium or large. So for most of the fields, it's working as expected. But for certain fields, the value is applied but not reflected
:host-context(.mediumFont) {
--fontSize: 11px;
}
:host-context(.largeFont) {
--fontSize: 12px;
}
:host-context(.smallFont) {
--fontSize: 10px;
}
refClassArray: RefClassInterface[] = [
{ class: 'font-small', refClass: 'smallFont' },
{ class: 'font-medium', refClass: 'mediumFont' },
{ class: 'font-large', refClass: 'largeFont' },
];
defaultFontSize = 'mediumFont';
changeFontSize(selector: string) {
this.defaultFontSize = selector;
let docBody = document.body;
console.log(document.getElementById(selector));
docBody.classList.add(selector);
this.refClassArray.forEach((refClass: RefClassInterface) => {
if (selector !== refClass.refClass) {
docBody.classList.remove(refClass.refClass);
document.querySelector('#' + refClass.refClass).setAttribute('style', 'font-weight: normal;' + 'pointer-events: auto;');
} else {
document.querySelector('#' + refClass.refClass).setAttribute('style', 'font-weight:' + 'bold;' + 'pointer-events: none;');
}
});
this.ieStyles.iEfont(selector);
}
Above is the logic I am using.
The first pic is from the element which is working fine. When I hover over the --font-size, 11px is reflected. The second one is the one where it's not working as expected and when I hover over the --font-size nothing is appearing. And both these elements are inside <body>
You should not be modifying your html code via direct access. This can open leave your application vulnerable to XSS Attacks for instance.
Instead, the Angular Team recomends the use of the Renderer2.
Taking your code and modifying it to use it, would lead to the following:
refClassArray: RefClassInterface[] = [
{ class: 'font-small', refClass: 'smallFont' },
{ class: 'font-medium', refClass: 'mediumFont' },
{ class: 'font-large', refClass: 'largeFont' },
];
defaultFontSize = 'mediumFont';
changeFontSize(selector: string, indexOfClassToAdd: number) {
this.defaultFontSize = selector;
const el: Element = document.getElementById(selector));
// Iterate each class in the list to remove it.
this.refClassArray.forEach((refClass: RefClassInterface) => {
// Remove the class from the specific element only if its present.
if (el.classList.contains(refClass.refClass) {
this.renderer2.removeClass(el, refClass.refClass);
};
});
this.renderer2.addClass(el, refClassArray[indexOfClassToAdd].refClass);
};
This would imply that you are aware of what is the class to be applied (and it being present in the style.scss or and the appropriate scss file).
Kind regards.
Out of curiosity, why are you doing it this way rather than simply using a class with the desired text size?
Either way, it looks like your value is not being applied because your selector is not specific enough.
To correct for this, you could make an artificially specific selector with something like this:
html > body * { // rules }
Why not use ngClass?
Define three classes
.font-small{
fontSize: 10px
}
.font-medium{
fontSize: 11px
}
.font-large{
fontSize: 12px
}
Bind something to the user select, like userChoice: SizeEnum or something
Then, on your data element, use ngClass to bind
ngClass = "{
'font-small': userChoice === sizeEnum.small,
'font-medium': userChoice === sizeEnum.medium,
'font-large': userChoice === sizeEnum.large
}"
The issue was resolved by moving
:host-context(.mediumFont) {
--fontSize: 11px;
}
:host-context(.largeFont) {
--fontSize: 12px;
}
:host-context(.smallFont) {
--fontSize: 10px;
}
from app.component.scss to styles.scss. Because the pop-ups are not a part of app.compontnet.html they render outside it.
I am porting a Polymer 3 app to lit-element stepwise and also want to replace the paper and iron elements by material web components. I very often am using the combination of paper-tabs and iron-pages to show property pages/dialogs.
What would be the replacement for paper-tabs/iron-pages in the material web components world?
I have found mwc-tab-bar but there is no example for actually displaying contents according to the selected tab.
Has anyone an example for how to build what sometimes is called a page-control (tabs plus contents)?
There are several options: (I would prefer 1 & 3)
You could just create a condition to render and eventually lazy load a certain page.
Use something like lion-steps (they also provide tabs)
Use a router like simple-wc-router
class MyElement extends LitElement {
static get properties() {
return {
page: String,
}
}
get _oneTemplate() {
return html`Page one`;
}
get _twoTemplate() {
return html`Page two`;
}
constructor() {
super();
this.page = 'one';
setTimeout(() => (this.page = 'two'), 5000);
}
render() {
return this.page === 'one' ? this._oneTemplate : this._twoTemplate;
}
}
I am trying to set up a color scheme in SCSS where I can have the following HTML:
<div class="swatch" data-bg="green">...</div>
I have a SCSS mixin defined as such:
#function color($key: 'black') {
#return map-get($colors, $key);
}
So, if I pass it background-color: color('green'), it will look at the $colors: ( ... ) map, see 'green': #009900, and return background-color: #009900; as the CSS.
The problem comes when I try to pass the data-bg attribute value into the color() SCSS mixin, like so:
.swatch[data-bg] {
background-color: color(attr(data-bg));
}
This doesn't work. I would expect it to parse the value as such:
color(attr(data-bg)) → color('green') → #009900
However, SCSS won't even render that background-color line in the CSS at all.
I have a Codepen where you can see what I'm trying to go for. It's the "Brown" color swatch here: https://codepen.io/rbrum/pen/axZLxw
Any help would be greatly appreciated.
For anyone else who happens across this question, here is how I ended up resolving my issue.
Instead of relying on data- attributes, I just relied on class names instead. Whenever I want an element with a certain background color, for instance, I use a class name like .bg-amber or .bg-purple. My colors are defined as such:
$colors: (
'black': #000000,
'white': #FFFFFF,
// ...
'amber': #FFBF00,
'purple': #800080,
// ...
);
To make it easier to access a color, I have defined a function that calls any color by name:
#function c($key: 'black') {
#return map-get($colors, $key);
}
I then define a mixin that, given a color name, will apply it as the background color. I can also pass it a prefix that is used in the CSS attribute.
#mixin bg($color-name, $prefix: '') {
.#{$prefix}#{$color-name} {
background-color: c($color-name);
}
}
If I wanted to use it in a one-off situation, I would use it like so:
#include bg('amber', 'bg-');
...which would generate the following:
.bg-amber {
background-color: #FFBF00;
}
Finally, I use an #each loop to do this for all of my colors:
#each $color-name, $color-val in $colors {
#include bg($color-name, 'bg-');
}
I can also define a "foreground" version:
#mixin fg($color-name, $prefix: '') {
.#{$prefix}#{$color-name} {
color: c($color-name);
}
}
And then I can use it in the #each loop right below the bg() usage:
#each $color-name, $color-val in $colors {
#include bg($color-name, 'bg-');
#include fg($color-name, 'txt-');
}
It can also be extended for things like border colors, box shadows, and more.
What is the canonical way to import styles into a web component?
The following gives me an error HTML element <link> is ignored in shadow tree:
<template>
<link rel="style" href="foo.css" />
<h1>foo</h1>
</template>
I am inserting this using shadow DOM using the following:
var importDoc, navBarProto;
importDoc = document.currentScript.ownerDocument;
navBarProto = Object.create(HTMLElement.prototype);
navBarProto.createdCallback = function() {
var template, templateClone, shadow;
template = importDoc.querySelector('template');
templateClone = document.importNode(template.content, true);
shadow = this.createShadowRoot();
shadow.appendChild(templateClone);
};
document.registerElement('my-nav-bar', {
prototype: navBarProto
});
Now direct <link> tag is supported in shadow dom.
One can directly use:
<link rel="stylesheet" href="yourcss1.css">
<link href="yourcss2.css" rel="stylesheet" type="text/css">
It has been approved by both whatwg and W3C.
Useful links for using css in shadow dom:
https://w3c.github.io/webcomponents/spec/shadow/#inertness-of-html-elements-in-a-shadow-tree
https://github.com/whatwg/html/commit/43c57866c2bbc20dc0deb15a721a28cbaad2140c
https://github.com/w3c/webcomponents/issues/628
Direct css link can be used in shadow dom.
If you need to place external styles inside the <template> tag you could try
<style> #import "../my/path/style.css"; </style>
however I have a feeling this will start importing after the element has been created.
Answer no longer valid
The #import syntax was removed from CSSStyleSheet.replace()
Chrome
Mozilla
Constructable Stylesheets
This is a new feature that allows for the construction of CSSStyleSheet objects. These can have their contents set or imported from a css file using JavaScript and be applied to both documents and web components' shadow roots. It will be available in Chrome with version 73 and probably in the near future for Firefox.
There's a good writeup on the Google developers site but I'll summarize it briefly below with an example at the bottom.
Creating a style sheet
You create a new sheet by calling the constructor:
const sheet = new CSSStyleSheet();
Setting and replacing the style:
A style can be applied by calling the methods replace or replaceSync.
replaceSync is synchronous, and can't use any external resources:
sheet.replaceSync(`.redText { color: red }`);
replace is asynchronous and can accept #import statements referencing external resources. Note that replace returns a Promise which needs to be handled accordingly.
sheet.replace('#import url("myStyle.css")')
.then(sheet => {
console.log('Styles loaded successfully');
})
.catch(err => {
console.error('Failed to load:', err);
});
Applying the style to a document or shadow DOM
The style can be applied by setting the adoptedStyleSheets attribute of either the document or a shadow DOM.
document.adoptedStyleSheets = [sheet]
The array in adoptedStyleSheets is frozen and can't be mutated with push(), but you can concatenate by combining with its existing value:
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
Inheriting from the document
A shadow DOM can inherit constructed styles from the document's adoptedStyleSheets in the same way:
// in the custom element class:
this.shadowRoot.adoptedStyleSheets = [...document.adoptedStyleSheets, myCustomSheet];
Note that if this is run in the constructor, the component will only inherit the style sheets that were adopted prior to its creation. Setting adoptedStyleSheets in the connectedCallback will inherit for each instance when it is connected. Notably, this will not cause an FOUC.
Example with Web Components
Let's create a component called x-card that wraps text in a nicely styled div.
// Create the component inside of an IIFE
(function() {
// template used for improved performance
const template = document.createElement('template');
template.innerHTML = `
<div id='card'></div>
`;
// create the stylesheet
const sheet = new CSSStyleSheet();
// set its contents by referencing a file
sheet.replace('#import url("xCardStyle.css")')
.then(sheet => {
console.log('Styles loaded successfully');
})
.catch(err => {
console.error('Failed to load:', err);
});
customElements.define('x-card', class extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open'
});
// apply the HTML template to the shadow DOM
this.shadowRoot.appendChild(
template.content.cloneNode(true)
);
// apply the stylesheet to the shadow DOM
this.shadowRoot.adoptedStyleSheets = [sheet];
}
connectedCallback() {
const card = this.shadowRoot.getElementById('card');
card.textContent = this.textContent;
}
});
})();
<x-card>Example Text</x-card>
<x-card>More Text</x-card>
NB!!!
THIS ANSWER IS OUTDATED
PLEASE CHECK THE ANSWER BY Himanshu Sharma
Up-to-date answer: https://stackoverflow.com/a/48202206/2035262
According to Polymer documentation:
Polymer allows you to include stylesheets in your <polymer-element> definitions, a feature not supported natively by Shadow DOM.
This is a bit weird reference, but I could not google the straight one. It looks like at the moment there is no rumors about supporting links inside templates.
That said, whether you want to use vanilla web component, you should either inline your css with <style> tag, or load and apply your css manually in javascript.
The above answers show how to import stylesheets into a web component, but importing a single style to a shadow DOM can be done (kind-of) programmatically. This is the technique I developed recently.
First - make sure that you embed your component-local styles directly in a template with the HTML code. This is to make sure that the shadow DOM will have a stylesheet in your element constructor. (importing other stylesheets should be ok, but you must have one ready in the constructor)
Second - use a css-variable to point at a css rule to import.
#rule-to-import {
background-color: #ffff00;
}
my-element {
--my-import: #rule-to-import;
}
Third - In the component constructor, read the CSS variable and locate the pointed to style in the document stylesheets. When found, copy the string but rewrite the selector to match the internal element(s) you wish to style. I use a helper function for this.
importVarStyle(shadow,cssvar,target) {
// Get the value of the specified CSS variable
const varstyle=getComputedStyle(this).getPropertyValue(cssvar).trim();
if(varstyle!="") varstyle: {
const ownstyle=shadow.styleSheets[0];
for(let ssheet of document.styleSheets) { // Walk through all CSS rules looking for a matching rule
for(let cssrule of ssheet.cssRules) {
if(cssrule.selectorText==varstyle) { // If a match is found, re-target and clone the rule into the component-local stylesheet
ownstyle.insertRule(
cssrule.cssText.replace(/^[^{]*/,target),
ownstyle.cssRules.length
);
break varstyle;
}
}
}
}
}
Try the <style> element inside of <template>:
<template>
<style>
h1 {
color: red;
font-family: sans-serif;
}
</style>
<h1>foo</h1>
</template>
I have tried google, but I can not definite answer.
I am trying to edit the styles of #sidebar:hover from mootools. My css is as follows:
#hoverzone:hover {
background: #EEE;
}
Now, I am trying to edit the background color from mootools when the page loads, to signify javascript is enabled. I know I can do:
$('hoverzone').addEvent('mouseenter', function(){
this.setStyle('background','000');
});
But I was wondering if there is a function I could call at the load of the page, that does this in one line with out the user doing anything.
Thanks
Edit: Yes, I was rushing, and anciently typed over instead of mouseenter
you cannot target a pseudo selector with javascript. so you need to create a new CSS rule that overrides the old one.
http://jsfiddle.net/dimitar/Z9RPP/
var StyleWriter = new Class({
// css classes on the fly, based on by Aaaron Newton's old work
createStyle: function(css, id) {
try {
if (document.id(id) && id) return;
var style = new Element('style', {id: id||'',type:'text/css'}).inject(document.getElements('head')[0]);
if (Browser.ie)
style.styleSheet.cssText = css;
else
style.set('text', css);
} catch(e) {
//console.log("failed:", e);
}
}
});
new StyleWriter().createStyle("#hoverzone:hover { background:red;}", "foo");
You can use the events domready or load of the windows object.
For example something like:
window.addEvent('load', function(){
$('hoverzone').setStyle('background-color','000');
});
Why use javascript. Why not just css:
#hoverzone { background: #000 }
#hoverzone:hover { background: #EEE }