Global Descendant-Only Styles in Svelte - javascript

Is there a way in Svelte to add styles that only affect the current component and any descendant components?
Svelte supports a native :global() selector wrapper which will declare styles for that selector in the global scope, but I am looking for something similar which only matches selectors in the current or any descendant components.
For example (REPL):
App.svelte
<script>
import C1 from './C1.svelte';
let name = 'world';
</script>
<div><C1>Hello {name}!</C1></div>
C1.svelte
<script>
import C2 from './C2.svelte';
let name = 'world';
</script>
<style>
:global(div) {
padding: 10px;
background-color: blue;
}
div {
background-color: red;
}
</style>
<div><C2><slot /></C2></div>
C2.svelte
<div><slot /></div>
In the above example, all three components receive the global styling from the middle child component, C1.svelte. I am looking for a way to do a sort of hybrid styling (not passing down styles to child components) to add "global-down" styles that only affect components downward in the component tree.
When the :global() selector wrapper is not used, matched nodes are assigned a unique class which the selector then targets, added to the selector during compilation. What I am asking/suggesting would be something like this:
:find(div) {
background-color: blue;
}
…where :find() similarly assigns a unique class to any HTML elements matched in the same or descending components. Is this possible?

You can scope styles to only child components by combining :global() with a scoped selector. For instance, the following selector will apply to all divs in any component that are the descendant of a div in this component.
<style>
div :global(div) {
padding: 10px;
background-color: blue;
}
</style>
The selector is transformed to something like this:
div.svelte-hash div { /* etc */ }
If you also want to also target top-level divs in this component, you could write the rule like this (though this may have CSS specificity implications):
<style>
div, div :global(div) {
padding: 10px;
background-color: blue;
}
</style>

Related

Styling a library component once it's been packaged?

How do we style library packaged component from via styles.scss?
For example suppose we have a <hello-component> and the template looks like this:
<div><h1 class="fs-HelloHeading">Hello!</h1><div>
How can be override the CSS inside fs-HelloHeading class and do it in a way that is context sensitive?
So for example if <hello-component> is inside <party-component> then it should have a yellow background, but if it's inside funeral-component then it should have a black background, and we would set these by overriding the styles in fs-HelloComponent. Thoughts?
My end goal is to override classes that are packaged with a component. For example I have these packaged with an Angular Material Table Component.
.mat-header-cell {
justify-content: left;
white-space: nowrap;
min-width: 12rem;
}
.mat-cell {
justify-content: left;
white-space: nowrap;
min-width: 12rem;
}
However I may want to change the width from outside the component later in a specific context, so I was thinking about doing that by adding additional css classes to the mat-row-element.
It is possible to override a style with the !important keyword. from top to bottom, the last !important will be applied. To set individual stylings depending on the surrounding element you can just 'mimic' the DOM-structure. Here is an example what you can put just on the end of the SCSS-file.
party-component {
hello-component {
background-color: yellow !important;
}
}
funeral-component {
hello-component {
background-color: black !important;
}
}
Please note that you have to replace colors and component-names with actual values.
Just try to define another custom selector with more or equal specificity to your CSS selectors.
if any rule is overridden,then you need to use !important flag to force your custom css rules, also consider that when you are using bootstrap, then some utilities classes have !important attribute.
<h1 id="custom-id" class="fs-HelloHeading">Hello {{ name }}!</h1>
style.css:
#custom-id {
color: blue;
}
you may want to use :host and ::ng-deep like
:host ::ng-deep .fs-HelloHeading { // in party-component css file
background-color: yellow;
}
:host ::ng-deep .fs-HelloHeading { // in funeral-component css file
background-color: black ;
}
it will look for all the child elements of these components
for more detail. Here's the docs: https://blog.angular-university.io/angular-host-context/

Understanding styled components component selector and ampersand

In styled-components docs, they have this example:
https://www.styled-components.com/docs/advanced#referring-to-other-components
It shows an Icon that changes color when you hover its parent, which is a link, in this case.
const Link = styled.a`
display: flex;
align-items: center;
padding: 5px 10px;
background: papayawhip;
color: palevioletred;
`;
const Icon = styled.svg`
flex: none;
transition: fill 0.25s;
width: 48px;
height: 48px;
${Link}:hover & { // <---- This is what I'm not understanding
fill: rebeccapurple;
}
`;
From the Docs, we know that:
Doc Note #1: styled-components solves this use case cleanly via the "component
selector" pattern. Whenever a component is created or wrapped by the
styled() factory function, it is also assigned a stable CSS class for
use in targeting.
And also that:
Doc Note #2: Ampersands (&) get replaced by our generated, unique classname for
that styled component, making it easy to have complex logic.
Let's analyze ${Link}:hover &
I know it gets translated into the browser as:
and:
I understand that sc-kAzzGY is the "stable CSS class" (Doc Note #1) that is created whenever an element is wrapped by the styled function.
I also know that the Ampersand (&) gets replaced by their generated unique classname (Doc Note #2) for that styled components. Hence, kDmLky is that class.
QUESTION
But what does the resulting selector (picture below) is actually selecting? Can anybody explain that to me?
${Link} is pointing to const Link i.e.: "Hovering my parent changes my style" which gets a class of sc-kAzzGY.
& is kinda like saying "And add this to the current class(es)/id(s)/etc."
So,
.my-class {
some-css: awesomeness;
&:hover {
more-css: extra-cool;
}
}
is equivalent to:
.my-class {
some-css: awesomeness;
}
.my-class:hover {
more-css: extra-cool;
}
Therefore, & points to the containing element const Icon i.e. the speech bubble and gets a class of kDmLky.
When Link is hovered, cause Icon to have fill: rebeccapurple
EDIT:
Just to clarify things a bit more:
When you have a declaration block inside of another declaration block like the example below, that inner declaration block becomes an independent one.
const Icon = styled.svg`
flex: none;
transition: fill 0.25s;
width: 48px;
height: 48px;
${Link}:hover & { // This declaraition block becomes an independent one
fill: rebeccapurple;
}
`;
And the result, in this case, is a declaration block with a selection that says:
When you have a class & which is descendent of the class ${Link} which is in the hover state, apply these rules:
fill: rebeccapurple;
NOTE: ${Link} refers to the Link class and & refers to the Icon class (svg).
${Link}:hover &
Here the ampersand & is the short hand way for referring to the top level component being defined, so I would read it as
${Link}:hover ${Icon}
i.e. it is referring to an Icon component contained inside of a Link component being hovered over
I would also recommend this link to see the more general use case for component selectors with styled components, where it's used in a parent child configuration for selection, and applied to the child

How to prevent Vue.js from automatically merging CSS classes?

I recently started working with Vue.js and I really enjoy it but I have come across something that I just can't figure out how to do. I have searched and read the documentation but found no solution.
When you add a CSS class to any of your components Vue.js will automatically add or merge the CSS classes with the root element in your child component.
This is a nice feature but I need to deactivate this feature for a specific component since I want the classes to be added to a child element of the root element.
I made this fiddle to illustrate http://jsfiddle.net/rasmuswoelk/9m2j0a9s/3
<div id="demo">
<child-component class="some-class"></child-component>
</div>
(The "some-class" is being added automatically and adds a green background color)
How can I prevent Vue.js from automatically merging CSS classes?
Updated
I disagree that it is intuitive to have a class that is applied to a component be moved by that component to an inner element, so my recommendation is still: Pass it as a prop.
However, it is possible to do what you want to do, with some caveats. The component will have a list of classes that belong on the outer element. In mounted, it looks at $el.classList and finds the classes that are not among the known outer element classes. Those get applied to the inner element and removed from the outer element. Caveat: if one of the outer element classes is applied to the component, it will not be moved to the inner element. And any updates to the applied class will not be caught.
Vue.component('child-component', {
template: `<div :class="outerClasses"><h1 :class="childClasses">Hello</h1></div>`,
data() {
return {
outerClasses: ['child'],
childClasses: []
};
},
mounted() {
this.childClasses = Array.from(this.$el.classList).filter((c) => !this.outerClasses.includes(c));
for (const c of this.childClasses) this.$el.classList.remove(c);
}
});
var demo = new Vue({
el: '#demo'
});
.child {
border: 1px solid red;
padding: 20px;
text-align: center;
}
.some-class {
background-color: green;
}
<script src="//vuejs.org/js/vue.min.js"></script>
<div id="demo">
<child-component class="some-class a b c"></child-component>
</div>
I think that you will want to wrap the whole thing in another <div></div> as Vue will merge the classes on the top element. If you just want the style for <h1> then try doing this:
template: `<div><h1 class="child">Hello</h1></div>`
This should give you the expected behavior, let me know if it doesn't work.
You can't abstract away the wrapper div that you have in your template, and trying to make the component work in such as way as though that div doesn't exist will only lead to confusion and incorrect styling.
Let's say that you managed to get your component to apply the outer class to a nested element in its template (which is what you want). Now when people use that component and apply a class to it, the style is applied to the input element and it is as though the whole component consists solely of the input element and there is no such wrapper div (this is the behavior you want).
The problem with this is that some styles will work (like background-color, or any appearance style), but other styles that affect the layout won't work (like position: absolute, because it will cause the input element to be positioned relative to the wrapper div which is not what was expected).
People who use that component should know about how the template is structured so they can style it correctly, because in reality there is a wrapper div there which they need to take into account.
I should mention you might be able to abstract away the wrapper div by using web components or the shadow DOM, but that's out of context of Vue.
A more simpler way (imo) is to bind the incoming class attribute and dynamic class prop to the data and remove them from the vnode (virtual node) during the created() hook.
Vue.component('child-component', {
template: `
<div class="child">
<h1 :class="staticClass">Hello</h1>
</div>
`,
data() {
return {
staticClass: [],
}
},
created() {
// dynamic :class prop
this.staticClass.push(this.$vnode.data.class)
// delete if not applying on this.$el
delete this.$vnode.data.class
// static class attr
this.staticClass.push(this.$vnode.data.staticClass)
// delete if not applying on this.$el
delete this.$vnode.data.staticClass
},
});
var demo = new Vue({
el: '#demo',
data: {
dynamicClass: 'dynamic-class'
},
});
.child {
border: 1px solid red;
padding: 20px;
text-align: center;
}
.some-class {
background-color: green;
}
.dynamic-class {
border: 1px solid yellow;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.min.js"></script>
<div id="demo">
<child-component :class="dynamicClass" class="some-class"></child-component>
</div>

Polymer 1.4.0 to 1.7.0, global :root styles no longer work

I have some styles that are meant to apply anywhere, including deep inside components. They're defined like this:
<style id="base-css" is="custom-style">
:root .primary {
color: red;
}
</style>
The result was that if I had, for example a <span class="primary"> anywhere, no matter how deep inside a Polymer component, it'd always apply. In fact, inspecting this span shows that the style was rewritten as:
:not([style-scope]):not(.style-scope):root .primary {
color: red;
}
Since I've updated my Polymer to 1.7.0, this no longer works. Styles defined this way no longer penetrate into components, and only work outside them. Adding a span with .primary to body and inspecting it shows that the style is now rewritten as:
html .primary:not([style-scope]):not(.style-scope) {
color: red;
}
Which, of course, wouldn't work inside a component, since all inside elements have .style-scope on them.
I read 1.7.0 release notes, and tried replacing :root with html, with exactly the same result.
Does anyone have any idea on how I can get this to work again?
Thank you.
Creating style hooks using CSS custom properties
You can tweak internal styles if you provide styling hooks using CSS custom properties. You create "style placeholders" inside the element that you can override from the main page.
Inside the main page:
<style>
base-css {
--primary: red;
}
</style>
Inside the element:
<style>
:host([background]) {
// Use --primary is not define, use #9E9E9E
background: var(--primary, #9E9E9E);
}
</style>
Documentation
Using CSS variables
Shadow DOM v1: Self-Contained Web Components

How to use a global parent selector with CSS Modules

I'm using CSS Modules within a React application. I also have a dropdown component with some global styles (which I'm happy with, as the general styles I want to re-use).
When the dropdown is active, a CSS class is applied (.dropdown--active). Is there a way I can include that global class alongside my component's locally scoped styles? i.e., what I'd like is for this to work:
.myClass {
color: red;
}
:global .dropdown--active .myClass {
color: blue;
}
However, that syntax makes the entire selector global, which is not what I'm after: I want .myClass to be scoped to the component.
just include the desired global class in parens:
:global(.dropdown--active) .myClass {
color: blue;
}

Categories