#HostBinding class assignment not respecting CSS :nth-child - css

I'm running into the following issue which I can't think of a way around:
I have a the following application structure:
AppComponent (access to Redux store)
||
CardStackComponent
||
CardComponent (many cards in a stack)
each CardComponent can have three states: accepted, rejected, or neither, represented by three classes: card--accepted, card--rejected, and card--in-stack.
There are buttons the user can click to accept or reject the topmost CardComponent, which bubbles an event up to AppComponent. AppComponent then uses a reducer to update the value of an accepted and rejected property on the Object that is used to render the CardComponent. In CardComponent I have three #HostBinding decorators which are used to add/remove classes from the CardComponent host element.
#HostBinding('class.card--accepted') accepted: boolean = false;
#HostBinding('class.card--rejected') rejected: boolean = false;
#HostBinding('class.card--in-stack') inStack: boolean = true;
the classes are being added/removed as expected as I hit the buttons to accept/reject the cards, however, there's one strange issue: To allow the cards to stack, I'm using CSS to position the top three cards absolutely on top of eachother, and hide any others. This works perfectly and is achieved using :nth-child
card--in-stack {
&:nth-child(1) {...}
&:nth-child(2) {...}
&:nth-child(3) {...}
&:nth-child(n+4) {...}
}
however, when the top card is accepted, the class card--in-stack is removed and the class card--accepted is added. This means that what was the second card in the stack (card--in-stack:nth-child(2)) should now be card--in-stack:nth-child(1), the one below it should now be card--in-stack:nth-child(2), and so on. Inspecting the second card in the stack (the one under the card that was just accepted), it still has the card--in-stack:nth-child(2) styles being applied, even through it is the first element on the page with class card--in-stack. Is there a way to get the CSS to respect :nth-child when changing classes on a component using the #HostBinding decorator?

Related

Style web component within shadow dom, depending on its parent

I am working on a component for uploading files. I have made two web components (wc):
wc-uploader the parent with the select files button.
wc-upload the children that are added to wc-uploader as file are added.
If the parent (wc-uploader) has the readonly or disabled attribute, I wish to style the wc-upload items differently.
I think the styling should be within the wc-upload component as it pertains to it.
This is the selector I tried within the wc-upload template but it does not work. I am guess it can't see beyond its shadow root.
wc-uploader[readonly] :host #close { /* here host = wc-uploader */
opacity: 0.5;
}
How would one style this element depending on its parent.
E.g. like if a select item is disabled, then it's option children are disabled too.
For loose coupling, so it doesn't matter when or where children Web Components are attached:
Make the children listen:
this.closest("wc-uploader").addEventListener("close",(evt)=>{
let parent = evt.detail; //evt.target could do
if parent.hasAttribute("close") ...
else ...
});
Then the parent reports its state:
attributeChangedCallback(name,oldValue,newValue){
if(name=="close" || name=="readonly" || name=="disabled"){
this.dispatchEvent(new CustomEvent(name, {
bubbles: false, // Event stays at wc-uploader
detail: this // wc-uploader
}));
}
}
If your children are deeper down in shadowRoots you need:
Custom Element getRootNode.closest() function crossing multiple (parent) shadowDOM boundaries
Or use document. as your "Event Bus", but then you have to be careful with your Event-names.
Be aware addEventListener attaches a listener outside the Web Component scope; so it is not garbage collected when the Component is removed; your task to remove them in the disconnectedCallback

How would I apply Material-UI managed styles to non-material-ui, non-react elements?

I have an application where I'm using Material UI and its theme provider (using JSS).
I'm now incorporating fullcalendar-react, which isn't really a fully fledged React library - it's just a thin React component wrapper around the original fullcalendar code.
That is to say, that I don't have access to things like render props to control how it styles its elements.
It does however, give you access to the DOM elements directly, via a callback that is called when it renders them (eg. the eventRender method).
Here's a basic demo sandbox.
Now what I'm wanting to do is make Full Calendar components (eg, the buttons) share the same look and feel as the rest of my application.
One way to do this, is that I could manually override all of the styles by looking at the class names it's using and implementing the style accordingly.
Or - I could implement a Bootstrap theme - as suggested in their documentation.
But the problem with either of these solutions, is that that:
It would be a lot of work
I would have synchronisation problems, if I made changes to my MUI theme and forgot to update the calendar theme they would look different.
What I would like to do is either:
Magically convert the MUI theme to a Bootstrap theme.
Or create a mapping between MUI class names and the calendar class names, something like:
.fc-button = .MuiButtonBase-root.MuiButton-root.MuiButton-contained
.fc-button-primary= .MuiButton-containedPrimary
I wouldn't mind having to massage the selectors etc to make it work (ie. For example - MUI Buttons have two internal spans, whereas Full Calendar have just one). It's mostly about when I change the theme - don't want to have to change it in two places.
Using something like Sass with its #extend syntax would is what I have in mind. I could create the full-calendar CSS with Sass easily enough - but how would Sass get access to the MuiTheme?
Perhaps I could take the opposite approach - tell MUI 'Hey these class names here should be styled like these MUI classes'.
Any concrete suggestions on how I would solve this?
Here is my suggestion (obviously, it's not straight forward). Take the styles from the MUI theme and generate style tag based on it using react-helmet. To do it event nicely, I created a "wrapper" component that do the map. I implemented only the primary rule but it can be extended to all the others.
This way, any change you will do in the theme will affect the mapped selectors too.
import React from "react";
import { Helmet } from "react-helmet";
export function MuiAdapter({ theme }) {
if (!theme.palette) {
return <></>;
}
return (
<Helmet>
<style type="text/css">{`
.fc-button-primary {
background: ${theme.palette.primary.main}
}
/* more styles go here */
`}</style>
</Helmet>
);
}
And the use of the adapter
<MuiAdapter theme={theme} />
Working demo: https://codesandbox.io/s/reverent-mccarthy-3o856
You could create a mapping between MUI class names and the calendar class names by going through ref's. It's possible that this is not what some would call "best practice"...but it's a solution :). Note that I updated your component from a functional component to a class component, but you could accomplish this with hooks in a functional component.
Add refs
Add a ref to the MUI element you want to set as a reference, in your case the Button.
<Button
color="primary"
variant="contained"
ref={x => {
this.primaryBtn = x;
}}
>
And a ref to a wrapping div around the component you want to map to. You can't add it directly to the component since that wouldn't give us access to children.
<div
ref={x => {
this.fullCal = x;
}}
>
<FullCalendar
...
/>
</div>
Map classes
From componentDidMount() add whatever logic you need to target the correct DOM node (for your case, I added logic for type and matchingClass). Then run that logic on all FullCalendar DOM nodes and replace the classList on any that match.
componentDidMount() {
this.updatePrimaryBtns();
}
updatePrimaryBtns = () => {
const children = Array.from(this.fullCal.children);
// Options
const type = "BUTTON";
const matchingClass = "fc-button-primary";
this.mapClassToElem(children, type, matchingClass);
};
mapClassToElem = (arr, type, matchingClass) => {
arr.forEach(elem => {
const { tagName, classList } = elem;
// Check for match
if (tagName === type && Array.from(classList).includes(matchingClass)) {
elem.classList = this.primaryBtn.classList.value;
}
// Run on any children
const next = elem.children;
if (next.length > 0) {
this.mapClassToElem(Array.from(next), type, matchingClass);
}
});
};
This is maybe a little heavy handed, but it meets your future proof requirement for when you updated update Material UI. It would also allow you to alter the classList as you pass it to an element, which has obvious benefits.
Caveats
If the 'mapped-to' component (FullCalendar) updated classes on the elements you target (like if it added .is-selected to a current button) or adds new buttons after mounting then you'd have to figure out a way to track the relevant changes and rerun the logic.
I should also mention that (obviously) altering classes might have unintended consequences like a breaking UI and you'll have to figure out how to fix them.
Here's the working sandbox: https://codesandbox.io/s/determined-frog-3loyf

Which CSS selector is used when a DragOver event is detected?

I am creating a custom RowFactory for my TableView to accept drag-and-drop files. I want to update the style of the specific Row when an acceptable DragOver event is detected.
Using :hover obviously won't work because that would apply even if the user is not dragging anything.
The end goal is simply to make it visually clear which row the user is about to drop the items onto.
Is there a selector I can use in my stylesheet to handle this? I could not find anything in the JavaFX CSS Reference Guide.
I can currently work around this by defining my own StyleClass and adding it in the setOnDragOver() method:
setOnDragOver(event -> {
// Determine if the dragged items are files
if (!this.isEmpty() && event.getDragboard().hasFiles()) {
event.acceptTransferModes(TransferMode.LINK);
this.getStyleClass().add("dragging");
}
});
However, attempting to remove the class when exiting does not seem to work:
setOnDragExited(event -> this.getStyleClass().remove("dragging"));
Edit: I should also clarify that each row may have other styles applied to them (based on several factors) and would want to ADD a style to the row when being dragged over, not replace all the rest)
As mentioned by #kleopatra, working with custom PseudoClass can work for you.
/**
* Interface to keep all custom pseudo classes.
*/
public interface Styles{
/** Dragged pseudo class. */
public static final PseudoClass DRAGGED_PSEUDOCLASS = PseudoClass.getPseudoClass("dragged");
}
In your code:
setOnDragOver(event -> {
if (!this.isEmpty() && event.getDragboard().hasFiles()) {
event.acceptTransferModes(TransferMode.LINK);
this.pseudoClassStateChanged(Styles.DRAGGED_PSEUDOCLASS,true);
}
});
setOnDragExited(event -> this.pseudoClassStateChanged(Styles.DRAGGED_PSEUDOCLASS,false));
In CSS:
.table-row-cell:dragged{
-fx-background-color:$custom-color;
}

CSS specificity testing

Are there any good tools or methods for automatic testing css selectors?
I'm developing a SCSS framework and would like to include automated tests in it.
Specifically I would like to have tests to ensure that the css selectors are working properly.
Say for instance that I have the html:
<input class="btn" disabled id="test"></input>
and css
.btn {
color: red;
...
}
.btn:disabled {
color: green;
...
}
I would like to have a test that ensures that the element above with id=test, have the .btn:disabled as the css class with highest priority (last class with highest specificity) and .btn as the second highest priority. In other words, I would like to ensure that the .btn:disabled and .btn css style is applied on the element and that styles in .btn:disabled are overwriting the styles in .btn.
I'm thinking of doing this in selenium. Are there any good ways of doing this? I would not like to hard code the css values into the tests.
The method I settled with is to use getComputedStyle to get the style with "highest priority". In the css I add a "tag" to the content property. In jasmine I then check if the desired tag is the computedStyle. (I will extend this in scss so that the content property is set by a mixin if test mode is used and not set in production.) This only makes a unit test for the class of highest priority, but not for the second highest etc.
Below is a tests to illustrate the example (only the first and last should pass).
// specs code
describe("CSS", function() {
it("Div element of class test should be handled by .test", () => {
const testdiv = document.getElementById("testdiv")
m = window.getComputedStyle(testdiv).getPropertyValue("content");
expect(m).toEqual('".test"');
});
it("Div element of class test should be handled by div", () => {
const testdiv = document.getElementById("testdiv")
m = window.getComputedStyle(testdiv).getPropertyValue("content");
expect(m).toEqual('"div"');
});
it("Div element should be handled by .test", () => {
const testdiv = document.getElementById("testdiv2")
m = window.getComputedStyle(testdiv).getPropertyValue("content");
expect(m).toEqual('".test"');
});
it("Div element of class test should be handled by div", () => {
const testdiv = document.getElementById("testdiv2")
m = window.getComputedStyle(testdiv).getPropertyValue("content");
expect(m).toEqual('"div"');
});
});
// load jasmine htmlReporter
(function() {
var env = jasmine.getEnv();
env.addReporter(new jasmine.HtmlReporter());
env.execute();
}());
.test {
content: '.test';
}
div {
content: 'div';
}
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>
<link href="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css" rel="stylesheet"/>
<div class="test" id="testdiv">TestDiv</div>
<div id="testdiv2">TestDiv</div>
If I understand the question correctly, you're basically asking for a test (for your example) like this:
if -> Element.ComputedStyle.color = green
then -> test passed
else -> test failed (you have CSS structure errors)
Obviously, the browser will interpret specificity correctly. So you're really testing if changes you made by adding/overriding CSS caused unintended visual consequences.
That seems pretty manual question to answer since you'll have to decide what each of those correct states is and then maintain the tests. If you go this route, I'd look at something like Backstop.js. Though CSS visual regression testing is REALLY complex so be careful how much you expect from it.
The Manual Way
Could you solve the problem somewhat manually by creating a SCSS variable that's usually transparent? Then as you're adding/changing code add that variable and change the color to something like pink that's really obvious? At this point, you should see where things override when you render the page.
If you're making a CSS framework, I'd test against your documentation since that should show you previous examples that would be overridden.
CSS Blocks
You may also want to look into the CSS Blocks API. It's not going to be a "test" exactly but the API provides scoping and compile errors that might help you catch some of those issues sooner than later.
Here's the pertinent part:
With CSS Blocks new resolution system, cascade conflicts will be caught for you before you even know they exist and you will never have to fight a specificity war ever again.
As you have mentioned you can achieve this with Selenium. In terms of methodology, if you're looking to maintain this long term then I would recommend following the Page Object Model. The official documentation on this is available here, and there are some other articles in various language here, here, and here.
Essentially what it boils down to is create classes or models for the pages (or page sections/components (as in a form that has multiple controls)), this class will then have properties/fields for each of the elements on the page that you want to interact with. The advantages of this approach are:
A single place to change if you need to update a selector (maintainability)
The underlaying code which can be ugly can be exposed through a nice interface that uses fluent syntax (readability)
How this looks (since you haven't specified a language I'll go with C#:
public class LoginPage
{
// FindBy attributes should decorate each property to specify the selector
public WebElement UsernameInput { get; set; }
public WebElement PasswordInput { get; set; }
public WebElement LoginButton { get; set; }
public LoginPage()
{
...
}
public LoginPage Load(this LoginPage page)
{
// code to navigate to the login page
}
public LoginPage EnterCredentials(this LoginPage page, string username, string password)
{
// code to fill out inputs
}
public HomePage Login(this LoginPage page)
{
// code to click login button
}
// Other methods
}
How this looks when you use it:
HomePage homePage =
new LoginPage()
.Load()
.EnterCredentials("user", "pass")
.Login();
// Now you can perform operations on the HomePage
CSS Specificity
As per the documentation Specificity is the logic by which the browser decides which CSS property values are the most relevant to an element incase there are two or more conflicting CSS rules that point to the same element and which will be applied. Specificity is calculated based on the matching rules which are composed as per different CSS selectors.
How to calculate Specificity
There are a couple of rules to calculate Specificity based on points which are as follows:
style attribute: 1000
id attribute: 100
class or pseudo-class: 1
Calculating the CSS Specificity
Let us calculate the Specificity of both the CSS
Sample A:
.btn {
color: red;
}
Explanation: Contains a class i.e. btn. So Specificity is 1.
Sample B:
.btn:disabled {
color: green;
...
}
Explanation: Contains a class i.e. btn and a pseudo-class i.e. disabled. So Specificity is 2.
Tests
The CSS specificity can also be visually verified through Specificity Calculator:
Conclusion
As the CSS sample B has a greater Specificity, so CSS sample B will be applied to the element:
<input class="btn" disabled id="test"></input>
Outro
However there are some more granular CSS Specificity Rules on:
Equal specificity: the latest rule counts.
ID selectors have a higher specificity than attribute selectors.
Contextual selectors are more specific than a single element selector.
A class selector beats any number of element selectors.
You can find a detailed documentation in CSS Specificity

How to update style when a window is scaled in angular

I have a chat window on my app and I want to update the size of an image in this chat window when the chat window is less than a certain width. Is there a way I can update the css style or class based on the width?
I'm using typescript and have the value of my cat window passed in:
#Input()
public chatWidth: number;
In my html, I was attempting to do something like this where I would apply a css class if the chatWidth property was less than 400:
<img *ngIf="upsell?.image?.url; let url" [src]="url" ng-class="{bigger-img : chatWidth < 400}">
However, this doesn't work and I don't seem to even get an error in my console
Use
<img *ngIf="upsell?.image?.url; let url" [src]="url" [ngClass]="{'bigger-img': chatWidth < 400}">
More info here on ngClass.
UPDATE
You can, I believe, wrap the condition in a 'method' that returns a boolean defined in your respective component and call it in the template instead of directly declaring it in the template. Here, in your case,
in component.ts,
checkChatWidth(): boolean {
return this.chatWidth < 400;
}
then, in your template,
<img *ngIf="upsell?.image?.url; let url" [src]="url" [ngClass]="{'bigger-img': checkChatWidth()}">
You have to take care of the possible 'null' checks within your 'method' that may arise due to not having a value for the 'chatWidth' input property based on your code setup.

Resources