Lit-element - :host selector is not triggering render on Safari - css

In a project I'm working on, I have this LitElement component, which is absolute-positioned and determines its left or top locations according to its reactive properties.
I've encountered a problem in Safari only, including iOS Safari/Chrome/Firefox. The element has updated its shadow styles, but in the view it does not move at all. I realized its a render issue when I found that when the cursor hovers the element, or exits the browser view, the element pops to the expected location.
I managed to reproduce the problem with a simpler code:
my-elem.ts:
import { LitElement, html, property, customElement, css } from 'lit-element';
#customElement('my-elem')
export class MyElem extends LitElement {
#property({ type: Number, reflect: true })
left: number = 30
static get styles() {
return css`
:host { position: absolute; }
div { position: absolute; }
`;
}
render() {
return html`
<style>
:host { left: ${this.left}px; }
</style>
<div> Hello </div>
`;
}
}
index.html:
<html lang="en">
<head>
<script src="my-elem.ts"></script>
</head>
<body>
<my-elem id="my-elem" left="50"></my-elem>
<button id="move-btn">move</button>
<script>
const elem = document.getElementById('ma-elem');
const moveBtn = document.getElementById('move-btn');
moveBtn.onclick = function() {
elem.left += 30;
}
</script>
</body>
</html>
I found that this happens only on :host selector. If the shadowed styling updates the div's left, it renders without any problems.
I wish to avoid forcing the browser to layout/paint, due to performance issues.

I think the root of your problem is that you are trying to use an expression inside a style element in your render template.
The LitElement guide mentions that using them that way has limitations and can cause performance issues. I think your problem is just facing one of them so you should remove that style element.
As an alternative, since you want to affect the host element, you can actually do this in an easier way if you just don't use a property but style the host directly.
So when using my-elem the code would look like:
<my-elem style="left: 30px;"></my-elem>
This is because styles applied to :host in shadow DOM have less priority than those applied to it using classes or the style attribute by its parent.
Alternatively, if you really want to keep the property, you can create property accessors for the left property and set the style to the host from there like this:
export class MyElem extends LitElement {
// other code
set left(value) {
const oldValue = this.left;
this._left = value;
this.style.setProperty('left', `${value}px`)
this.requestUpdate('left', oldValue);
}
get left() {
return this._left;
}
}

Related

Vaadin-flow: Css stylesheet import for custom components with shadow root element

I created a server-side component with a shadow-root element.. Is it possible to import a style sheet for the elements within that shadow-root? The CssImport annotation does not work, and I couldn't find anything similar, that could work?!
I could create a static String and add an element, but a css-file-import would be better?! (and of course I could use the component without a shadow-root, but the question was "is it possible" ... )
MyCustomComponent.java
#Tag("my-custom-component")
#CssImport("./components/my-custom-component.css")
public class MyCustomComponent extends Component {
public MyCustomComponent() {
super();
ShadowRoot shadow = getElement().attachShadow();
Span span = new Span();
span.getElement().setAttribute("part", "caption");
Div div = new Div();
div.getElement().setAttribute("part", "content");
shadow.appendChild(span.getElement());
shadow.appendChild(div.getElement());
}
}
my-custom-component.css
:host [part='caption'] {
background-color: red;
}
:host [part='content'] {
background-color: blue;
}
I'm curious why you would want a shadow root around a Flow component, as it doesn't really provide any benefits other than CSS encapsulation.
The #CssImport annotation with the themeFor parameter won't help you in this case, as that only works with Web Components using ThemableMixin (https://github.com/vaadin/vaadin-themable-mixin/).
I'm not sure whether it's possible to load css into a shadow root with Flow, but as long as you have part attributes on all elements you want to style, you can do that with a regular (non-shadow-dom) stylesheet, like so:
my-custom-component::part(caption) {
color: red;
}
Just put that in your styles.css or wherever you have your app's normal global css.

Vue conditional style tag in single file component

I have started development on a vue web component library. Members of my team asked for the potential to remove default styles via an HTML attribute on the web component. I know that I could use CSS class bindings on the template elements, however, I was wondering if there is a way to conditionally include the style tag itself so that I would not need to change the class names in order to include the base styles or not.
Example of a component's structure
<template>
<section class="default-class" />
</template>
<script>
export default {
props: {
useDefault: Boolean
}
}
</script>
<style>
// Default styles included here
// Ideally the style tag or it's content could be included based off useDefault prop
</style>
Potential implementation
<web-component use-default="false"></web-component>
As I read your question; you want to keep <style> both affecting Global DOM and shadowDOM
One way is to clone those <style> elements into shadowDOM
But maybe ::parts works better for you; see: https://meowni.ca/posts/part-theme-explainer/
customElements.define("web-component", class extends HTMLElement {
constructor() {
super()
.attachShadow({mode:"open"})
.innerHTML = "<div>Inside Web Component</div>";
}
connectedCallback() {
// get all styles from global DOM and clone them inside the Web Component
let includeStyles = this.getAttribute("clone-styles");
let globalStyles = document.querySelectorAll(`[${includeStyles}]`);
let clonedStyles = [...globalStyles].map(style => style.cloneNode(true));
this.shadowRoot.prepend(...clonedStyles);
}
});
<style mystyles>
div {
background: gold
}
</style>
<style mystyles>
div {
color: blue
}
</style>
<div>I am Global</div>
<web-component clone-styles="mystyles"></web-component>

Make css variables bleed to children components

I am using Polymer 2 to build a library of components, but I am having issues with css variables in browsers other than Chrome.
I have a wrapper component (x-page) and it has a theme property that can be either light or dark. My css looks similar to this:
:host([light]) {
---color-1: rgb(200,200,200);
---color-2: rgb(210,210,210);
}
:host([dark]) {
---color-1: rgb(10,10,10);
---color-2: rgb(20,20,20);
}
I now want to use these variables in all components inside this wrapper (not only directly slotted, all of them including their shadow-root.
In Chrome this works fine, as the children will read the variables from the wrapper, but in other browsers it doesn't seem to work even though I am using the apply-shim polyfill and tried with the custom-styles as well.
I appreciate your help :)
In order that CSS styles can be applied with the Shadow DOM polyfill, at first you must prepare the <template> you append to the shadow root with the ShadyCSS.prepareTemplate() function, as explained in the ShadyCSS polyfill page:
ShadyCSS.prepareTemplate( template1, 'x-page' )
Example:
ShadyCSS.prepareTemplate( template1, 'x-page' )
class XPage extends HTMLElement {
constructor() {
super()
this.attachShadow( { mode: "open" } )
.appendChild( template1.content.cloneNode( true ) )
}
}
customElements.define( 'x-page', XPage )
<script src="https://rawgit.com/webcomponents/webcomponentsjs/master/webcomponents-bundle.js"></script>
<template id="template1">
<style>
:host([light]) {
---color-1: red;
---color-2: pink;
}
:host([dark]) {
---color-1: yellow;
---color-2: black;
}
span {
color: var(---color-1);
background-color: var(---color-2);
}
</style>
<span><slot></slot></span>
</template>
<x-page light>Light</x-page>
<x-page dark>Dark</x-page>

How to limit style to component level in React?

My app tries to show emails. Sometimes the style in the email will affect the app itself.
Right now I am using the package juice to inline style in the email html. However, sometimes it cannot inline correctly. So I try to find other solutions.
I know Angular automatically add some random string in each class to make sure style in one component won't affect other component, is there a same way to do it in React? Or is there other way to limit style to the component level without using iframe? Thanks
The demo shows the p { color: red; } from the email also affects the app itself. In this case, it affects Content in app.
Live demo
class Mail extends Component {
render() {
// the style inside is from mail, in real case, it can have tens or even hundreds of different styles
const mailFromServer = `
<html>
<head>
<style>
p { color: red; }
</style>
</head>
<body>
<p>Content in mail</p>
</body>
</html>
`;
return (
<div>
<div dangerouslySetInnerHTML={{__html: mailFromServer}} />
</div>
);
}
}
class App extends Component {
render() {
return (
<div>
<Mail />
<p>Content in app</p>
</div>
);
}
}
There are few ways to do this.One of the way would be by passing style to the elements and defining styles as objects. For example
const styles = {
content: {
color: '#000',
backgroundColor: '#fafafa',
},
};
class Mail extends React.Component {
render() {
return() {
<p style={{styles.content}}> Content </p>
}
}
}
If you really want something scalable then you can use styled-components which for me personally work really nicely and fulfills all your styling needs.
You'll need to use a class to limit the style to a specific component's features:
import React, { Component } from 'react';
import { render } from 'react-dom';
class Mail extends Component {
render() {
const mail = `
<html>
<head>
<style>
.mail-header { color: red; }
</style>
</head>
<body>
<h1 class="mail-header">Heading in mail</h1>
</body>
</html>
`;
return (
<div>
<div dangerouslySetInnerHTML={{__html: mail}} />
</div>
);
}
}
export default Mail;
Styles in <style>..</style> or from CSS sheet is global. They are applied across your app.
If you have control over how the email is formatted, I would recommend setting different class names for different email types.
If you have a limited set of email types, you can specify the styles for each of them in a css sheet, and import it on your html file or Webpack.
styles.css
.important-email { color: red; }
.not-important-email { color: blue; }
// ...
// styles for different email types
Mail.jsx
class Mail extends Component {
render() {
const mail = `
<html>
<head>
</head>
<body>
<h1 class="important-email">Heading in mail</h1>
</body>
</html>
`;
return (
<div>
<div dangerouslySetInnerHTML={{__html: mail}} />
</div>
);
}
}
export default Mail;
Yes, you can use CSS Modules, which is one or more css files (written in js) in which all class names are auto-scoped locally to each component by default.
There is a good article about this here https://medium.com/#pioul/modular-css-with-react-61638ae9ea3e
You can also try to reset those known html entities which are commonly over-ridden by your Mail html. You may have to play around a bit, but, given your example, you could do something like the following:
https://stackblitz.com/edit/react-neymbb
Basically, in my sandbox I reset your paragraph color and padding to their defaults, as well as, used it in conjunction with another inline style. Depending on the entire construct of css inheritance in your app, your results may vary, but you should be able to get something workable.
More information on the css values I recommend found here:
https://developer.mozilla.org/en-US/docs/Web/CSS/initial

Change style of pseudo elements in angular2

Is it possible to change style of pseudo elements using [style] or [ngStyle] in angular2?
in order to get a blur effect on a div acts like an overlay, and I should set up background-image on pseudo element.
I tried something like
<div class="blur" [style.before.backgroundImage]="'url('+ featuredImage[i] + ' )'">
it didn't work. I also tried this
<div class="blur" [ngStyle]="'{:before{ background-image:url('+ featuredImage[i] + ' )}}'">
You can achieve what you need with CSS variables.
In your style sheet you can set the background image like this:
.featured-image:after { content: '';
background-image: var(--featured-image);
}
After that you can programmatically set this variable on the same element or higher up in the DOM tree:
<div class="featured-image" [ngStyle]="{'--featured-image': featuredImage}">
More about CSS variables here: https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables Note that the browser support is not complete yet.
Also note that you will need to sanitize the url/style using sanitizer.bypassSecurityTrustResourceUrl(path) or sanitizer.bypassSecurityTrustStyle('--featured-image:url(' + path + ')')):
No it's not possible. It is actually not an Angular issue: pseudo elements are not part of DOM tree, and because of that do not expose any DOM API that can be used to interact with them.
Usual approach if you want to deal with pseudo elements programmatically is indirect: you add/remove/change class and in CSS make this class affect corresponding pseudo-element. So in your case you could have one more class that changes necessary style:
.blur:before {/* some styles */}
.blur.background:before {/* set background */}
Now all you need to do is to toggle .background class on the element when you need before pseudo-element to get a background. You can use NgClass, for example.
if you want to add other properties I did it like this:
<div class="progress" [style]= "'--porcentaje-width:' + widthh " ></div>
and the css:
.progress::after {
content: '';
width: var(--porcentaje-width);
}
this worked for me :)
With current versions of Angular 2+ you can use CSS Variables to achieve this as well as sanitizing your input.
In your style sheet define the rule using CSS Variables. A fallback can also be defined as CSS Variables aren't supported by IE.
https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties
.featured-image:after {
content: '';
// Fallback for IE
background-image: url('fallback-img.png');
background-image: var(--featured-image);
}
Rather than bypassing security trust style, you can also sanitize your input with a reusable pipe:
https://angular.io/api/platform-browser/DomSanitizer#sanitize
import {Pipe, PipeTransform, SecurityContext} from '#angular/core';
import {DomSanitizer, SafeStyle} from '#angular/platform-browser';
#Pipe({
name: 'safeStyle',
})
export class SafeStylePipe implements PipeTransform {
constructor(protected sanitizer: DomSanitizer) {}
transform(value: string): SafeStyle {
if (!value) return '';
return this.sanitizer.sanitize(SecurityContext.STYLE, value);
}
}
In your template:
<div class="featured-image" [style.--featured-image]="featuredImage[i] | safeStyle"></div>

Resources