Adding CSS custom properties dynamically to svelte components 3.38.0 - javascript

Svelte added support for passing CSS custom properties to a component in 3.38.0
Which allows us to pass a css property like shown below.
<Drawer --test1="red">
Technically it makes a div like this around your component code:
<div style="display: contents; --test: red;">
What I would like to do is to pass multiple without having to define them like --test-1="X" --test-2="Z" etc.
I would like to place them in an object:
let test = {
'--test-1': "X",
'--test-2': "Z"
};
And let the keys be rendered with there values.
The question is, is there a way to achieve this?
Link to REPL with a way to do this would be great.
Kind regards,
Tom

I don't think that is currently possible. It would be useful, so you should open a feature request.
In the meantime, you can solve this yourself with a custom wrapper component. See this REPL (relevant code below).
<!-- App.svelte -->
<script>
import Example from './Example.svelte';
import CustomPropWrapper from './CustomPropWrapper.svelte';
const customProps = {
'--first-color': 'red',
'--second-color': 'green'
}
</script>
<!-- longhand way -->
<Example --first-color={firstColor} --second-color={secondColor}></Example>
<!-- pass as an object using a wrapper -->
<CustomPropWrapper {customProps}>
<Example />
</CustomPropWrapper>
<!-- CustomPropWrapper.svelte -->
<script>
export let customProps = {};
// create an inline style string
$: style = Object.entries(customProps).reduce((acc, [key, value]) => `${acc}; ${key}: ${value}`, '');
</script>
<div {style}>
<slot />
</div>
<style>
div {
display: contents;
}
</style>
<!-- Example.svelte -->
<p class="first">
I'm the first paragraph
</p>
<p class="second">
I'm the second paragraph
</p>
<style>
.first {
color: var(--first-color);
}
.second {
color: var(--second-color);
}
</style>

Related

Styling with dynamically used class names and attributes in JS(React)?

I wonder if there is a feasible way to implement something like that.
Say, I want to specify padding but I don't want to use inline styling and want to use classes instead. But I also don't want to specify all possible values in CSS.
Like I write:
<Container className="p25 m10" />
what can be rendered to the self-generated classes p25 and m10
<div class="p25 m10"/> /* Classes p25 and m10 are self-generated and have padding and margin */
or even
<Container p=25, m=10 />
rendered into the same or at least to
<div style="padding: 25px; margin: 10px"/>
I don't see that there is a way to auto-generate a css class. But,
this your first option, are classes that represent some kind of pure css style, what you can do is create a class like this:
.p25 {
padding: 25px;
}
<Container className="p25"/>
Your second option, are props that inside the functional component, through some logic, you will carry out the implementation, for example:
export default function Container({p, m}) {
return (
<div style={{padding: `${p}px`}, margin: `${m}px`}></div>
)
}
install tailwind and config manual stuff into config file.
module.exports = {
theme: {
extend: {
spacing: {
'25': '25px',
}
}
}
}
then use it this way
<div class="p-[25]">
<!-- ... -->
</div>

VueJS: dynamic class name inside style tags

I have a VueJS application which contains some Materializecss modals, all wrapped within single page components. Due to the nature of the application I have to assign dynamic unique IDs to each modal. Below is a snippet:
<template>
<div :id="modal_id" :class="'modal '+modal_id">
<div class="modal-content">
.... stuff here
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat">Exit</a>
</div>
</div>
</template>
<style type="text/css" scoped>
.modal {
width: 90% !important;
height: 100% !important;
}
.modal_id
{
background-color: black;
}
</style>
<script type="text/javascript">
import pdf from 'vue-pdf'
export default {
data: function() {
return {
modal_id: 'ViewPdf_'+this.$root.g(), // this.$root.g() returns a unique integer
}
},
}
</script>
My question is if it's possible to use Vue to modify the custom class name from within <style></style> tags to match the class name generated when component is mounted. If not, what workarounds I could use?
<style type="text/css" scoped>
.modal {
width: 90% !important;
height: 100% !important;
}
.modal_id // <--- I wish this class name was the same with value of this.modal_id
{
background-color: black;
}
</style>
If the CSS is all contained in your Vue component, you could just produce the style information in your component rather than trying to match up the css selector based on the DOM id.
Specifically, instead of doing this:
<div :id="modal_id" :class="'modal '+modal_id">
Do this:
<div :id="modal_id" :style="modalStyle">
and then within your component, have a computed property for the style, using the guidelines at https://v2.vuejs.org/v2/guide/class-and-style.html#Binding-Inline-Styles
For example:
computed: {
modalStyle: function () {
return {backgroundColor: 'black'};
}
}
You can build a computed property that will react to changes in modal_id and calculate a class name that you want to attach to particular modal.
<template>
<div :id="modal_id" :class="className">
...
</div>
</template>
<script type="text/javascript">
export default {
data: function() {
return {
}
},
computed: {
className: function () {
if (this.modal_id === <your modal_id1>) return 'class1';
else if (this.modal_id === <your modal_id2>) return 'class2';
},
modal_id: function () {
return 'ViewPdf_'+this.$root.g()
}
}
}
</script>
<style>
.class1{}
.class2{}
</style>
the modal_id also should be a computed property if you want to make it dynamic.
Yes, you can edit the css property name with javascript if you use an external css file. Here is a simple example in pure javascript that shows how I change the name of the css name, thereby placing its style on the second paragraph.
//the index of the stylesheet based on load order
var cssIndex = 0;
var cssRules = (document.all) ? 'rules' : 'cssRules';
function changeClass() {
for (i = 0, len = document.styleSheets[cssIndex][cssRules].length; i < len; i++) {
if (document.styleSheets[cssIndex][cssRules][i].selectorText === ".text") {
document.styleSheets[cssIndex][cssRules][i].selectorText = ".text2";
return;
}
}
}
.text {
text-align: center;
}
<button onclick='changeClass()'> Change css property class name </button>
<p id="1" class="text">This is text class</p>
<p id="2" class="text2"> This is text2 class</p>
I hope you can apply it in your Vue code.

Is it possible to remove an html element and keep your children with javascript?

I have the following structure .. I would like to remove div.son but keepdiv.grandson, is that possible ?! or changing your <tag> would also be a solution .. ex: changing from <fieldset> to a <div>, remembering that I do not have access to HTML, every change must be done using ** javascript **!
<div class="father">
<fieldset class="son">
<div class="grandson">Content here</div>
<div class="grandson">Content here</div>
<div class="grandson">Content here</div>
<div class="grandson">Content here</div>
</fieldset>
</div>
I tried to use the removeChild () function of ** javascript **, but it removes the entire element.
It's possible with vanilla JavaScript by deep cloning the node of grandson before removing anything else. and then appending it back to the parent. Of course if you want to place it somewhere else, you need to append needed logic of DOM traversing. (CSS section is only for visual validation of the result)
const grandson = document.querySelector('.grandson');
const father = grandson.closest('.father');
const clonedGrandson = grandson.cloneNode(true);
father.querySelector('.son').remove();
father.appendChild(clonedGrandson);
.father {
background-color: red;
padding: 20px;
}
.son {
background-color: blue;
padding: 20px;
}
.grandson {
background-color: green;
padding: 20px;
}
<div class="father">
<fieldset class="son">
<div class="grandson">
<p>Save me</p>
</div>
</fieldset>
</div>
You may take a look at this answer, try to use the search bar next time.
https://stackoverflow.com/a/170056/10944905
In case you just want to jump all over the answer.
var cnt = $(".remove-just-this").contents();
$(".remove-just-this").replaceWith(cnt);

How to target a component in svelte with css?

How would I do something like this:
<style>
Nested {
color: blue;
}
</style>
<Nested />
i.e. How do I apply a style to a component from its parent?
You need to pass props to the parent component with export let, then tie those props to class or style in the child component.
You can either put a style tag on the element in the child you want to style dynamically and use a variable you export for the parent to determine the value of a style directly, then assign the color on the tag like this:
<!-- in parent component -->
<script>
import Nested from './Nested.svelte';
</script>
<Nested color="green"/>
<!-- in Nested.svelte -->
<script>
export let color;
</script>
<p style="color: {color}">
Yes this will work
</p>
Upside here is flexibility if you only have one or two styles to adjust, downside is that you won't be able to adjust multiple CSS properties from a single prop.
or
You can still use the :global selector but just add a specific ref to the element being styled in the child like so:
<!-- in parent component -->
<script>
import Nested from './Nested.svelte';
</script>
<Nested ref="green"/>
<style>
:global([ref=green]) {
background: green;
color: white;
padding: 5px;
border-radius: .5rem;
}
</style>
<!-- in Nested.svelte -->
<script>
export let ref;
</script>
<p {ref}>
Yes this will work also
</p>
This ensures global only affects the exact ref element inside the child it's intended for and not any other classes or native elements. You can see it in action at this REPL link
The only way I can think of is with an additional div element.
App.svelte
<script>
import Nested from './Nested.svelte'
</script>
<style>
div :global(.style-in-parent) {
color: green;
}
</style>
<div>
<Nested />
</div>
Nested.svelte
<div class="style-in-parent">
Colored based on parent style
</div>
Multiple Nested elements
You could even allow the class name to be dynamic and allow for different colors if you use multiple Nested components. Here's a link to a working example.
You could use inline styles and $$props...
<!-- in parent component -->
<script>
import Nested from './Nested.svelte';
</script>
<Nested style="background: green; color: white; padding: 10px; text-align: center; font-weight: bold" />
<!-- in Nested.svelte -->
<script>
let stylish=$$props.style
</script>
<div style={stylish}>
Hello World
</div>
REPL
using :global(*) is the simplest solution.
No need to specify a class in the child if you want to style all immediate children for example
In the parent component:
<style>
div > :global(*) {
color: blue;
}
<style>
<div>
<Nested />
<div>
Nested will be blue.
I take a look and found nothing relevant (maybe here), so here is an alternative by adding <div> around your custom component.
<style>
.Nested {
color: blue;
}
</style>
<div class="Nested">
<Nested />
</div>
Maybe you will found something but this one works.
The way I do it is like this:
<style lang="stylus">
section
// section styles
:global(img)
// image styles
</style>
This generates css selectors like section.svelte-15ht3eh img that only affects the children img tag of the section tag.
No classes or tricks involved there.

How to use CSS "target" selector in web components using lit html

I am using lit html to create custom web components in my project. And my problem is when I try to use the CSS target selector in a web component it wont get triggered, but when I am doing it without custom component the code works perfectly. Could someone shed some light to why this is happening and to what would be the workaround for this problem? Here is my code:
target-test-element.js:
import { LitElement, html} from '#polymer/lit-element';
class TargetTest extends LitElement {
render(){
return html`
<link rel="stylesheet" href="target-test-element.css">
<div class="target-test" id="target-test">
<p>Hello from test</p>
</div>
`;
}
}
customElements.define('target-test-element', TargetTest);
with the following style:
target-test-element.css:
.target-test{
background: yellow;
}
.target-test:target {
background: blue;
}
and I created a link in the index.html:
index.html(with custom component):
<!DOCTYPE html>
<head>
...
</head>
<body>
<target-test-element></target-test-element>
Link
</body>
</html>
And here is the working one:
index.html(without custom component)
<!DOCTYPE html>
<head>
...
</head>
<body>
Link
<div class="target-test" id="target-test">
Hello
</div>
</body>
</html>
LitElement uses a Shadow DOM to render its content.
Shadow DOM isolates the CSS style defined inside and prevent selecting inner content from the outide with CSS selectors.
For that reason, the :target pseudo-class won't work.
Instead, you could use a standard (vanilla) custom element instead of the LitElement.
With no Shadow DOM:
class TargetTest extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div>
<span class="test" id="target-test">Hello from test</span>
</div>`
}
}
customElements.define('target-test-element', TargetTest)
.test { background: yellow }
.test:target { background: blue }
<target-test-element></target-test-element>
Link
Alternately, if you still want to use a Shadow DOM, you should then set the id property to the custom element itself. That supposes there's only one target in the custom element.
class TargetTest extends HTMLElement {
connectedCallback() {
this.attachShadow( { mode: 'open' } ).innerHTML = `
<style>
:host( :target ) .test { background-color: lightgreen }
</style>
<div>
<span class="test">Hello from test</span>
</div>`
}
}
customElements.define('target-test-element', TargetTest)
<target-test-element id="target-test"></target-test-element>
Link
Bit in late, i've experienced the same problem! So i'm following one of two paths:
Use a lit element but without the shadowDOM, to do that in your Lit element call the method createRenderRoot()
createRenderRoot () {
return this
}
Instead handle the CSS logic with :target i'm handling the attribute on the element (easy to do with Lit) eg. active and use it in CSS:
element[active] {
/* CSS rules */
}
These are my solutions for the moment! Hope it's help...

Categories