How to use a CSS framework with LitElement - javascript

I would like to use the CSS framework Bulma with LitElement. I know I can use an External Stylesheet However, they state it is bad practice to do it this way. I also have the problem that I had to import it into each element, which doesn't feel right.
So I copied the whole Bulma file content into a js module, but the styles are not applied after importing it.
import { css } from 'lit-element'
export default css`
#-webkit-keyframes spinAround {
from {
transform: rotate(0deg);
}
...
Importing the style as link tag works but is as mentioned bad practice.
import { LitElement, html, css } from 'lit-element'
import './src/table.js'
import styles from './styles.js'
class LitApp extends LitElement {
constructor() {
super()
this.tHeader = ['status', 'name', 'alias']
this.data = [
['connect', 'john', 'jdoe'],
['disconnect', 'carol', 'carbon'],
['disconnect', 'mike', 'mkan'],
['disconnect', 'tina', 'tiba'],
]
}
static get styles() {
return [
styles, // does not work
css`
:host {
padding: 5vw;
min-height: 100vh;
}
table.table {
width: 100%;
}`
]
}
render() {
return html`
<link rel="stylesheet" href="./node_modules/bulma/css/bulma.min.css">
<div class="columns">
<div class="column is-8 is-offset-2">
<div class="card">
<div class="card-content">
<agent-table .theader=${this.tHeader} .data=${this.data}></agent-table>
</div>
</div>
</div>
</div>`
}
}
customElements.define('lit-app', LitApp)
Furthermore, the Table does not receive the styles and I had to import the file again, which I would like to avoid.
class AgentTable extends LitElement {
constructor() {
super()
this.tHeader = []
this.data = []
}
static get properties() {
return {
theader: { type: Array },
data: { type: Array },
}
}
render() {
return html`
<table class="table is-narrow">
<thead>
<tr>${this.tHeader.map((header) => html`<td class="is-capitalized">${header}</td>`)}</tr>
</thead>
<tbody>
${this.data.map((row) => html`<tr>
${row.map((cell) => html`<td class="is-capitalized">${cell}</td>`)}
</tr>`)}
</tbody>`
}
}
customElements.define('agent-table', AgentTable)
I generally struggle to find a good approach to apply a CSS framework. I read back and forth on this page https://lit-element.polymer-project.org/guide/styles but I don't get it to work.
Sorry, If this is a bit ambiguous or hard to understand I struggle to voice this question properly.

There are a few problems with importing styles with the link tag. This is why it's a bad practice:
A new stylesheet will be created every time an instance of your element is created. LitElement uses constructable stylesheets (if supported) to share a single stylesheet instance across all elements.
The stylesheet is loaded after the element is rendered, so there can be a flash-of-unstyled-content
The ShadyCSS polyfill won't work
The href attribute is relative to the main document
Adding few thousands css rules to a custom element that only needs a few it's not the way it's meant to be. CSS frameworks should be split by custom-elements.
That said, you can do what you did there and copy the whole Bulma inside a js module. The problem it's that you have to escape the character "" of that CSS.
css`
.breadcrumb li + li::before {
color: #b5b5b5;
content: "\0002f";
}
`
This doesn't work as expected but doesn't fail because template literals allow you to read the raw strings as they were entered, without processing escape sequences. So there aren't any JS errors.
Escape those and it'll be fine:
css`
.breadcrumb li + li::before {
color: #b5b5b5;
content: "\\0002f";
}
`

Note, the browser support for the below is currently awful. As far as I understand, it only runs on chrome by default. Firefox has it implemented, but you need to start Firefox with an additional flag.
In this post by the creator of svelte, the styling problem also comes up. There are some fascinating bits to take away from this whole discussion.
As hinted by some a while ago and already been under suspicion, I had another go at the problem.
So I am using adopted stylesheets now in this variant. As showcased here.
This seems like a legit way in some cases.
'use strict';
// define some web component
class WebComponent extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.text = ''
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<!-- setting some custom css just to see if it stays -->
<style> p { padding: 1rem; background-color: pink; } </style>
<!-- this is expected to become large font size from tailwindcss -->
<p class="title is-size-1">${this.text}</p>`
}
}
customElements.define('web-component', WebComponent)
// create two instances of the component to see if adopted style
// will change for both, meaning they are both referencing the same
// thing in memory
const webComponentA = document.createElement('web-component')
webComponentA.text = 'cowsay'
const webComponentB = document.createElement('web-component')
webComponentB.text= 'web'
// construct a blank style sheet
const sheet = new CSSStyleSheet();
(async () => {
// get some css styles as text, maybe with fetch if purely in the browser
// or via module resolver like rollup & webpack
const url = 'https://cdn.jsdelivr.net/npm/bulma#0.9.2/css/bulma.min.css'
const response = await fetch(url)
await sheet.replace(await response.text());
// set the styles on both components and add them to the dom
webComponentA.shadowRoot.adoptedStyleSheets = [sheet];
webComponentB.shadowRoot.adoptedStyleSheets = [sheet];
document.body.append(webComponentA, webComponentB)
})().catch(console.warn);
// some time later, purge the style sheet
// and watch both components loosing the framework styles
setTimeout(() => sheet.replaceSync(``), 3000);
Unfortunately, one has to use a constructed style sheet like shown above. It doesn't work if you try to give the document root style sheet. I.E.
webComponent.shadowRoot.adoptedStyleSheets = [document.StyleSheet.item(0)]
DOMException: Failed to set the 'adoptedStyleSheets' property on 'ShadowRoot': Can't adopt non-constructed stylesheets.

Related

Vue styling is not appearing properly inside slot when used scoped with style tags in Vue 3

actually I am a bit new to Vue JS and currently working with Vue 3.
I am making use of Oruga library to make components in Vue 3. Now I am making use of storybook to make different components in Vue 3.
The vue file code in it is written as
<template>
<o-radio v-bind="$props" v-model="model">
<slot />
</o-radio>
</template>
<script lang="ts">
import { defineComponent } from "#vue/runtime-core";
import Radio from '../mixins/radio-mixins';
export default defineComponent({
name: "BaseRadio",
computed: {
model: {
get() {
return this.$props.nativeValue;
},
set(value: any) {
this.$emit("input", value);
},
},
},
emits: ["input"],
props: {
...Radio.props,
},
});
</script>
<style scoped>
.b-radio.radio.is-primary .check:checked {
border-color: var(--color-blue);
}
.b-radio.radio.is-primary .check:checked:focus {
box-shadow: 0 0 3px 3px var(--color-light-blue);
}
.b-radio.radio.is-primary .check:before {
background: var(--color-blue);
}
</style>
Here in <style></style> tags I am modifying the element classes provided by oruga library to achieve the desired styling.
The main problem is that while applying scoped in the styling does not apply any styles into the rendered view, but omitting scoped from <style></style tag does.
How can I fix this I need to apply these styles along with using the scoped tag in <style></style ?.
Maybe it's because you are trying to change classes that only exist in the DOM itself because the Oruga thing that you are using, so when you omit the "scoped" property, you are able to manipulate oruga classes globally. These oruga classes doesn't exist in your component itself when you're working with the component

Using custom Angular component inside Contentful rich text renderer

I have a rich text element in Contentful that has an embedded entry within it. I am attempting to have that embedded entry render inside a custom Angular component. However, when I pass in options to the Contentful documentToHtmlString function, it displays the tag, but does not render anything or even trigger the console.log() function I have inside the custom component typescript file.
TYPESCRIPT for the rendering
convertToHTML(document:any[]) {
const options = {
renderNode: {
[BLOCKS.EMBEDDED_ENTRY]: (node:any, next:any) => `<embed-element [node]="${node}" [content]="${next(node.content)}"></embed-element>`
}
}
let unsafe = documentToHtmlString(document, options);
return this.sanitizer.bypassSecurityTrustHtml(unsafe);
}
HTML for the rendering
<span [innerHTML]="convertToHTML(article.fields.content)"></span>
I have loaded the custom element <embed-element></embed-element> in the providers section of the app.module.ts file as well.
import { EmbedElementComponent } from './shared/components/embed-element/embed-element.component';
#NgModule({
declarations: [
...
EmbedElementComponent
],
providers: [
...
EmbedElementComponent
]
})
Then inside typescript file for the custom element, I just simply have a console.log in the onInit function for testing purposes. I am not seeing this occur in the console.
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'embed-element',
templateUrl: './embed-element.component.html',
styleUrls: ['./embed-element.component.css']
})
export class EmbedElementComponent implements OnInit {
#Input() node: any;
#Input() content: any;
constructor() { }
ngOnInit(): void {
console.log(this.node);
}
}
And in the HTML for the custom element, I removed the <p> tag just in case it was stripping it out as "unsafe" and replaced it with the following:
EMBEDDED ELEMENT WORKS!!!!!
Finally, I see nothing appear on screen for this custom element once everything is rendered. Inside the inspect element however, this is what I get.
<embed-element [node]="[object Object]" [content]=""></embed-element>
How do I manage to make the custom element actually get called in this aspect? And at least receive the console log message I am requesting inside the custom element?
Not sure if you still needed another solution, but I came across ngx-dynamic-hooks https://github.com/MTobisch/ngx-dynamic-hooks - and was able to reuse my custom components within the inner html.
const cmpt_data = {
title: 'This is a test'
};
const headerFooterRichTextOption: Partial<Options> = {
renderNode: {
["embedded-entry-inline"]: (node, next) => `${this.embeddedContentfulEntry(node.nodeType, node.data)}`,
["paragraph"]: (node, next) => `<span>${next(node.content)}</span>`,
}
};
const d = documentToHtmlString(tt.value, headerFooterRichTextOption);
const hSanit = this.sanitizer.bypassSecurityTrustHtml(d);
embeddedContentfulEntry(nodeType: any, data: any) {
return "<custom-component [title]='context.title'></custom-component>";
}
<ngx-dynamic-hooks [content]="hSanit" [context]="cpmt_data"></ngx-dynamic-hooks>
I believe that using a custom component for this type of situation was not working because it was rendering outside of Angulars scope or after initial components initiation.
I resolved this issue by essentially just removing the custom element all together and creating a function that renders the embedded element as I wanted.
// Notice the new function call, renderCustomElement()
convertToHTML(document:any[]) {
const options = {
renderNode: {
[BLOCKS.EMBEDDED_ENTRY]: (node:any, next:any) => this.renderCustomElement(node)
}
}
let unsafe = documentToHtmlString(document, options);
return this.sanitizer.bypassSecurityTrustHtml(unsafe);
}
And finally that function that simply creates some basic html with Bootstrap styling and returns that back to be rendered.
renderCustomElement(node:any) {
let link = `/support-center/article/${node.data.target.fields.category[0].sys.id}/${node.data.target.sys.id}`;
return `<style>.card:hover{box-shadow: 0 0.5rem 1rem rgb(0 0 0 / 15%) !important;}</style><a class="card clickable mb-2 text-decoration-none text-dark" href="${link}" target="_blank"><div class="card-header">Article</div><div class="card-body"><h4 class="fw-light">${node.data.target.fields.title}</h4><p class="fw-light pb-0 mb-0">${node.data.target.fields.preview}</p></div></a>`
}

Pass a stylesheet to a Lit-Element Web component

Is it possible to pass a stylesheet to a web component using lit-element?
I mean in a similiar way to the one used to set up the properties of the web component.
For example, I have this web component and I want to pass it, from the outside, a stylesheet that has to be pushed to the array returned inside "static get styles" after "SharedStyles".
class MyComponent extends LitElement {
static get properties() {
return {
name: { type: String }
};
}
static get styles () {
return [
super.styles,
SharedStyles,
css`
`
];
}
}
If I want to set the property "name" i do:
<my-component .name="${"Fred"}"></my-component>
Is there a way to pass a stylesheet to my-component?
Yes you can. But you need to export css file first.
step 1: create styles.js
import { css } from 'lit-element';
export const styleSheet = css`
:host{
// global css
}
.cssProp1{
// your properties
}
.cssProp2{
// your properties
}
`
Step 2: import css in to your component
import { styleSheet } from styles.js;
static get styles(){
return styleSheet;
}
Step 3: render html:
render(){
return html`
<div class="cssProp1"></div>
<div class="cssProp2"></div>
`
}
This will do the job

Can I set styles to document body from styled-components?

I want to know if there is a possibility to cast styles from styled-components to wrapping element, like <body> tag in the way that looks like this:
class SomePageWrapper = styled.div`
body {
font-size: 62.5%
}
`
No, but you can use the global inject function to set stuff on your body like this:
import { injectGlobal } from 'styled-components';
injectGlobal`
#font-face {
font-family: 'Operator Mono';
src: url('../fonts/Operator-Mono.ttf');
}
body {
margin: 0;
}
`;
The example is from here: https://www.styled-components.com/docs/api#injectglobal
As it turns out - you can't set styled components to outer elements. This violates the philosophy of encapsulation - a benefit from styled-components.
So the way to do this would be to add a new class to body element called classList via JS in the parent component with componentDidMount() and remove it with componentWillUnmount().
For v4 styled-components, use createGlobalStyle .
import { createGlobalStyle } from 'styled-components'
const GlobalStyle = createGlobalStyle`
body {
color: ${props => (props.whiteColor ? 'white' : 'black')};
}
`
// later in your app
<React.Fragment>
<GlobalStyle whiteColor />
<Navigation /> {/* example of other top-level stuff */}
</React.Fragment>
I think you can't do that.
class SomePageWrapper = styled.body`
font-size: 62.5%
`
So,when you put <SomePageWrapper/> in the render,it turns to be <body></body>.Then you need to put it in the root part,to replace <body>.
But how do you replace the <body> you have in index.html.When you can't replace it,you will end up two <body> in browser,or something weird will happen.
Just simply use css file for

Changing body styles in vue router

I'm using Vue router with two pages:
let routes = [
{
path: '/',
component: require('./components/HomeView.vue')
},
{
path: '/intro',
component: require('./components/IntroView.vue')
}
]
This works fine, except that each of my components has different body styling:
HomeView.vue:
<template>
<p>This is the home page!</p>
</template>
<script>
export default {
}
</script>
<style>
body {
background: red;
}
</style>
IntroView.vue:
<template>
<div>
<h1>Introduction</h1>
</div>
</template>
<script>
export default {
}
</script>
<style>
body {
background: pink;
}
</style>
My goal is to have these two pages have different background styles (eventually with a transition between them). But at the moment when I go to the home route (with the red background), then click the intro route, the background colour stays red (I want it to change to pink).
Edit:
index.html:
<body>
<div id="app">
<router-link to="/" exact>Home</router-link>
<router-link to="/intro">Introduction</router-link>
<router-view></router-view>
</div>
<script src="/dist/build.js"></script>
</body>
I got it working with the lifecycle hook beforeCreate and a global stylesheet. In global.css:
body.home {
background: red;
}
body.intro {
background: pink;
}
In the <script> section of HomeView.vue:
export default {
beforeCreate: function() {
document.body.className = 'home';
}
}
And similar in IntroView.vue.
watch: {
$route: {
handler (to, from) {
const body = document.getElementsByTagName('body')[0];
if (from !== undefined) {
body.classList.remove('page--' + from.name.toLowerCase());
}
body.classList.add('page--' + to.name.toLowerCase());
},
immediate: true,
}
},
Another fairly simple solution, add it to your base App.vue file. The to.name can be replaced with to.meta.class or similar for something more specific. This is a nice do it once and it works forever type solution though.
If the class is view specific, may be this will help
methods: {
toggleBodyClass(addRemoveClass, className) {
const el = document.body;
if (addRemoveClass === 'addClass') {
el.classList.add(className);
} else {
el.classList.remove(className);
}
},
},
mounted() {
this.toggleBodyClass('addClass', 'mb-0');
},
destroyed() {
this.toggleBodyClass('removeClass', 'mb-0');
},
Move the methods section to a mixin and then the code can be DRY.
Alternatively you can use this
vue-body-class NPM
vue-body-class GitHub
It allows to control your page body classes with vue-router.
Wrote this when faced the similar issue.
It also refers to Add a class to body when component is clicked?
I ran into an issue when I wanted to modify the styles of the html and body tags along with the #app container on specific routes and what I found out is that for various reasons, this can be quite complicated.
After reading through:
#Saurabh's answer on another relative question: https://stackoverflow.com/a/42336509/2110294
#Mteuahasan's comment above regarding Evan You's suggestion
#GluePear's / OP's answer to this question: https://stackoverflow.com/a/44544595/2110294
Sass style inclusion headaches: https://github.com/vuejs/vue-loader/issues/110#issuecomment-167376086
In your App.vue (could be considered as the centralised state):
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'my-app',
methods: {
handleStyles () {
// Red style to the body tag for the home page
if (['/'].includes(this.$route.path)) document.body.className = 'bg-red'
// Pink style to the body tag for all other pages
else if (document.body.classList.contains('bg-red')) document.body.className = 'bg-pink'
}
},
// Handle styles when the app is initially loaded
mounted () {
this.handleStyles()
},
// Handle styles when the route changes
watch: {
'$route' () {
this.handleStyles()
}
}
}
</script>
<style>
.bg-red {
background: red;
}
.bg-pink {
background: pink;
}
</style>
So for the route / you get the red style and for all other routes the pink style is applied.
The handleStyles logic could have been dealt with by the beforeCreated hook however in my case, this would only affect the html and body styles but the #app element where the router view is rendered into would only available when the dom has been mounted so I think that it is a slightly more extensible solution.
Top answer is right, but need some optimization.
Because that answer doesn't work when one refreshes that page. Reason is that dom is not loaded done when set the style you want.
So, better solution is this:
beforeCreate() {
this.$nextTick(() => {
document.querySelector('body').style.backgroundColor = '#f2f2f2'
})
},
beforeDestroy() {
document.querySelector('body').style.backgroundColor = ''
},
By wrapping style setting handler in this.$nextTick, the style will be set when dom is loaded. So you can get correct styles when refresh page
You can also do it directly in the router file using the afterEach hook:
mainRouter.afterEach((to) => {
if (["dialogs", "snippets"].includes(to.name)) {
document.body.style.backgroundColor = "#F7F7F7";
// or document.body.classList.add(className);
} else {
document.body.style.backgroundColor = "#FFFFFF";
// or document.body.classList.remove(className);
}
});
afterEach hook documentation
to is a route object which contains the route name (if named), path, etc. Documentation for all the props
You can use scoped attribute in the style element. Then the style will be limited only to that vue file.
HomeView.vue:
<template>
<p>This is the home page!</p>
</template>
<script>
export default {
}
</script>
<style scoped>
body {
background: red;
}
</style>
IntroView.vue:
<template>
<div>
<h1>Introduction</h1>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
body {
background: pink;
}
</style>

Categories

Resources