Mixins exists check in scss which is imported dynamically is not working - html

For the implementation of RTL and LTR styles using scss I created two scss files _ltr.scss and _rtl.scss and based on the country selection of user in the application, I dynamically import the corresponding stylesheet.
_ltr.scss
$start-direction: left;
#mixin paddingStart($val) {
padding-#{$start-direction}: $val;
}
_rtl.scss
$start-direction: right;
#mixin paddingStart($val) {
padding-#{$start-direction}: $val;
}
main.js //where country switch happening
const root = document.documentElement;
root.classList.remove('rtl');
root.classList.add('ltr');
if (USER COUNTRY DIR VALUE === 'RTL') {
root.classList.remove('ltr');
root.classList.add('rtl');
}
_main.scss
html.ltr {
#import '_ltr.scss';
}
html.rtl {
#import '_rtl.scss';
}
#if mixin-exists(paddingStart) { //NOT WORKING
#include paddingStart(10px)
}
This is how I dynamically import the scss, and it works correctly. But the mixin exists if statement is always failing. Is there any ways to make the mixin work as expected in SCSS? Or any options to implement this LTR and RTL style better way. Because in my case user can change the country and based on his selection I have to load the styles dynamically.

SCSS is compiled before arriving to the browser and deployed, Javascript is run after it arrived to the browser. You are trying to modify SCSS with Javascript something that doesn't really exist.
What you need is to have both rtl.scss ltr.scss loaded at the same time, and apply the rules depending on the direction.
For rtl the easier way is to create a mixin where you can set/modify rules if the site is in rtl.
#mixin rtl {
body:dir(rtl) &{
#content;
}
}

You shouldn't type .scss when importing files. https://sass-lang.com/documentation/at-rules/import#importing-css
html.ltr {
#import 'ltr';
}
html.rtl {
#import 'rtl';
}

Related

turning hard coded value in scss window size query into a variable [duplicate]

I am trying to use CSS variables in media query and it does not work.
:root {
--mobile-breakpoint: 642px;
}
#media (max-width: var(--mobile-breakpoint)) {
}
From the spec,
The var() function can be used in place of any part of a value in
any property on an element. The var() function can not be used as
property names, selectors, or anything else besides property values.
(Doing so usually produces invalid syntax, or else a value whose
meaning has no connection to the variable.)
So no, you can't use it in a media query.
And that makes sense. Because you can set --mobile-breakpoint e.g. to :root, that is, the <html> element, and from there be inherited to other elements. But a media query is not an element, it does not inherit from <html>, so it can't work.
This is not what CSS variables are trying to accomplish. You can use a CSS preprocessor instead.
As Oriol has answered, CSS Variables Level 1’s var() cannot currently be used in media queries. However, there have been recent developments that will address this problem. Once CSS Environment Variables Module Level 1 is standardized and implemented, we’ll be able to use env() variables in media queries in all modern browsers.
The CSS Working Group (CSSWG) codified env() in a new standard (currently at a draft stage): the CSS Environment Variables Module Level 1 (see this GitHub comment and this comment for more info). The draft calls out variables in media queries as an explicit use case:
Because environment variables don’t depend on the value of anything drawn from a particular element, they can be used in places where there is no obvious element to draw from, such as in #media rules, where the var() function would not be valid.
If you read the specification and have a concern, or if you want to voice your support for the media-query use case, you can do so in issue #2627, in issue #3578, or in any CSS GitHub issue labeled with “css-env-1”.
GitHub issue #2627 and GitHub issue #3578 are devoted to custom environmental variables in media queries.
Original answer from 2017-11-09:
Recently, the CSS Working Group decided that CSS Variables Level 2 will support user-defined environment variables using env(), and they will try to make them be valid in media queries. The Group resolved this after Apple first proposed standard user-agent properties, shortly before the official announcement of iPhone X in September 2017 (see also WebKit: “Designing Websites for iPhone X” by Timothy Horton). Other browser representatives then agreed they would be generally useful across many devices, such as television displays and ink printing with bleed edges. (env() used to be called constant(), but that has now been deprecated. You might still see articles that refer to the old name, such as this article by Peter-Paul Koch.) After some weeks passed, Cameron McCormack of Mozilla realized that these environment variables would be usable in media queries, and Tab Atkins, Jr. of Google then realized that user-defined environment variables would be especially useful as global, non-overridable root variables usable in media queries. Now, Dean “Dino” Jackson of Apple will join Atkins in editing Level 2.
You can subscribe to updates on this matter in w3c/csswg-drafts GitHub issue #1693 (for especially relevant historical details, expand the meeting logs embedded in the CSSWG Meeting Bot’s resolutions and search for “MQ”, which stands for “media queries”).
What you can do however is #media query your :root statement!
:root {
/* desktop vars */
}
#media screen and (max-width: 479px) {
:root {
/* mobile vars */
}
}
Totally works in Chrome, Firefox and Edge at least the latest production versions as of this posting.
One limitation: if you need to access the value as a variable – for example to use in calculations elsewhere – you will need to have a variable, and it requires defining the variable in two places: the media query and variable declaration.
Apparently it's just not possible to use native CSS variables like that. It's one of the limitations.
A clever way to use it is to change your variables in the media-query, to impact all your style. I recommend this article.
:root {
--gutter: 4px;
}
section {
margin: var(--gutter);
}
#media (min-width: 600px) {
:root {
--gutter: 16px;
}
}
One way to achieve what you want is using npm package postcss-media-variables.
If you are fine with using npm packages then you can take a look documentation for same here:
postcss-media-variables
Example
/* input */
:root {
--min-width: 1000px;
--smallscreen: 480px;
}
#media (min-width: var(--min-width)) {}
#media (max-width: calc(var(--min-width) - 1px)) {}
#custom-media --small-device (max-width: var(--smallscreen));
#media (--small-device) {}
The level 5 specification of media queries define Custom Media Queries that does almost what you are looking for. It allows you to define breakpoint similar to how you do with CSS variables and later use them in different places.
Example from the specification:
#custom-media --narrow-window (max-width: 30em);
#media (--narrow-window) {
/* narrow window styles */
}
#media (--narrow-window) and (script) {
/* special styles for when script is allowed */
}
There is still no support for this actually so we have to wait before using this feature.
Short Answer
You can use JavaScript to change the value of media queries and set it to the value of a css variable.
// get value of css variable
getComputedStyle(document.documentElement).getPropertyValue('--mobile-breakpoint'); // '642px'
// search for media rule
var mediaRule = document.styleSheets[i].cssRules[j];
// update media rule
mediaRule.media.mediaText = '..'
Long Answer
I wrote a small script which you can include on your page. It replaces every media rule with a value of 1px with the value of the css variable --replace-media-1px, rules with value 2px with --replace-media-2px and so on. This works for the media queries with, min-width, max-width, height, min-height and max-height even when they are connected using and.
JavaScript:
function* visitCssRule(cssRule) {
// visit imported stylesheet
if (cssRule.type == cssRule.IMPORT_RULE)
yield* visitStyleSheet(cssRule.styleSheet);
// yield media rule
if (cssRule.type == cssRule.MEDIA_RULE)
yield cssRule;
}
function* visitStyleSheet(styleSheet) {
try {
// visit every rule in the stylesheet
var cssRules = styleSheet.cssRules;
for (var i = 0, cssRule; cssRule = cssRules[i]; i++)
yield* visitCssRule(cssRule);
} catch (ignored) {}
}
function* findAllMediaRules() {
// visit all stylesheets
var styleSheets = document.styleSheets;
for (var i = 0, styleSheet; styleSheet = styleSheets[i]; i++)
yield* visitStyleSheet(styleSheet);
}
// collect all media rules
const mediaRules = Array.from(findAllMediaRules());
// read replacement values
var style = getComputedStyle(document.documentElement);
var replacements = [];
for (var k = 1, value; value = style.getPropertyValue('--replace-media-' + k + 'px'); k++)
replacements.push(value);
// update media rules
for (var i = 0, mediaRule; mediaRule = mediaRules[i]; i++) {
for (var k = 0; k < replacements.length; k++) {
var regex = RegExp('\\((width|min-width|max-width|height|min-height|max-height): ' + (k+1) + 'px\\)', 'g');
var replacement = '($1: ' + replacements[k] + ')';
mediaRule.media.mediaText = mediaRule.media.mediaText.replace(regex, replacement);
}
}
CSS:
:root {
--mobile-breakpoint: 642px;
--replace-media-1px: var(--mobile-breakpoint);
--replace-media-2px: ...;
}
#media (max-width: 1px) { /* replaced by 642px */
...
}
#media (max-width: 2px) {
...
}
You can build a media query programmatically using matchMedia:
const mobile_breakpoint = "642px";
const media_query = window.matchMedia(`(max-width: ${mobile_breakpoint})`);
function toggle_mobile (e) {
if (e.matches) {
document.body.classList.add("mobile");
} else {
document.body.classList.remove("mobile");
}
}
// call the function immediately to set the initial value:
toggle_mobile(media_query);
// watch for changes to update the value:
media_query.addEventListener("change", toggle_mobile);
Then, instead of using a media query in your CSS file, apply the desired rules when body has the mobile class:
.my-div {
/* large screen rules */
}
.mobile .my-div {
/* mobile screen rules */
}
As you can read other answers, still not possible to do so.
Someone mentioned custom environmental variables (similar to custom css variables env() instead of var()), and the principle is sound, though there are still 2 major issues:
weak browser support
so far there is no way to define them (but probably will be in the future, as this is so far only an unofficial draft)

Using CSS Variables on Stripe Elements

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
}
}
});

select all child elements except H1 and H2? [duplicate]

I'm trying to select input elements of all types except radio and checkbox.
Many people have shown that you can put multiple arguments in :not, but using type doesn't seem to work anyway I try it.
form input:not([type="radio"], [type="checkbox"]) {
/* css here */
}
Any ideas?
Why :not just use two :not:
input:not([type="radio"]):not([type="checkbox"])
Yes, it is intentional
If you're using SASS in your project, I've built this mixin to make it work the way we all want it to:
#mixin not($ignorList...) {
//if only a single value given
#if (length($ignorList) == 1){
//it is probably a list variable so set ignore list to the variable
$ignorList: nth($ignorList,1);
}
//set up an empty $notOutput variable
$notOutput: '';
//for each item in the list
#each $not in $ignorList {
//generate a :not([ignored_item]) segment for each item in the ignore list and put them back to back
$notOutput: $notOutput + ':not(#{$not})';
}
//output the full :not() rule including all ignored items
&#{$notOutput} {
#content;
}
}
it can be used in 2 ways:
Option 1: list the ignored items inline
input {
/*non-ignored styling goes here*/
#include not('[type="radio"]','[type="checkbox"]'){
/*ignored styling goes here*/
}
}
Option 2: list the ignored items in a variable first
$ignoredItems:
'[type="radio"]',
'[type="checkbox"]'
;
input {
/*non-ignored styling goes here*/
#include not($ignoredItems){
/*ignored styling goes here*/
}
}
Outputted CSS for either option
input {
/*non-ignored styling goes here*/
}
input:not([type="radio"]):not([type="checkbox"]) {
/*ignored styling goes here*/
}
Starting from CSS Selectors 4 using multiple arguments in the :not selector becomes possible (see here).
In CSS3, the :not selector only allows 1 selector as an argument. In level 4 selectors, it can take a selector list as an argument.
Example:
/* In this example, all p elements will be red, except for
the first child and the ones with the class special. */
p:not(:first-child, .special) {
color: red;
}
Unfortunately, browser support is somewhat new.
I was having some trouble with this, and the "X:not():not()" method wasn't working for me.
I ended up resorting to this strategy:
INPUT {
/* styles */
}
INPUT[type="radio"], INPUT[type="checkbox"] {
/* styles that reset previous styles */
}
It's not nearly as fun, but it worked for me when :not() was being pugnacious. It's not ideal, but it's solid.
If you install the "cssnext" Post CSS plugin, then you can safely start using the syntax that you want to use right now.
Using cssnext will turn this:
input:not([type="radio"], [type="checkbox"]) {
/* css here */
}
Into this:
input:not([type="radio"]):not([type="checkbox"]) {
/* css here */
}
https://cssnext.github.io/features/#not-pseudo-class

Importing styles into a web component

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>

Call a function in Compass/SASS

I want to create a function in SASS that generates different classes.
Something like this
#function test($class-name) {
#for $i from 1 through $tot-class {
.#{$class-name}-#{$i} {
//some rules
}
}
}
but i can't figure how to call this function.
I've tried with
#test(red);
or
test(red);
but it doesn't seem to work.
Which is the right way?
The main problem here is that you don't actually want to use a function, you want a mixin. The difference is that functions don't contain any CSS rules - they simply return a value (which you can assign to a variable or use in a CSS property declaration). Mixins, on the other hand, have no return value and can contain full-blown CSS rules to be added when that mixin is included into the SASS document. Here's what your example would look like as a mixin:
#mixin test($class-name) {
#for $i from 1 through $tot-class {
.#{$class-name}-#{$i} {
//some rules
}
}
}
You'd then include the mixin later by using:
#include test(red);