This question already has answers here:
Why do pseudoclasses on the host element have to be inside of the host function?
(2 answers)
Closed 3 years ago.
Is it not possible, or not allowed, to combine :host and :defined in CSS, while combining the latter with the :host() pseudoclass works?
As you can see in below example, the following
:host:defined { display: block; }
does not work, while
:host(:defined) { display: block; }
works.
class CustomElement extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'closed' });
const css = `
:host { display: none; }
:host:defined { display: block; }
`;
this.styles = document.createElement('style');
this.styles.innerHTML = css;
}
connectedCallback() {
const div = document.createElement('div');
div.innerHTML = `<code><${this.tagName.toLowerCase()}></code> connected!`;
this.shadow.appendChild(this.styles);
this.shadow.appendChild(div);
}
}
class OtherCustomElement extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'closed' });
const css = `
:host { display: none; }
:host(:defined) { display: block; }
`;
this.styles = document.createElement('style');
this.styles.innerHTML = css;
}
connectedCallback() {
const div = document.createElement('div');
div.innerHTML = `<code><${this.tagName.toLowerCase()}></code> connected!`;
this.shadow.appendChild(this.styles);
this.shadow.appendChild(div);
}
}
customElements.define('custom-element', CustomElement);
customElements.define('other-custom-element', OtherCustomElement);
<custom-element></custom-element>
<other-custom-element></other-custom-element>
The above code example on codepen: https://codepen.io/connexo/pen/GRKEGax
From the specification we can read:
The :host pseudo-class, when evaluated in the context of a shadow tree, matches the shadow tree’s shadow host. In any other context, it matches nothing
The :host() function pseudo-class has the syntax: :host( <compound-selector-list> )
When evaluated in the context of a shadow tree, it matches the shadow tree’s shadow host if the shadow host, in its normal context, matches the selector argument. In any other context, it matches nothing.
Basically, :host will match the shadow host and nothing more. You cannot combine it with any other selector While the second syntax allow you to add a selector inside ().
If you refer to the example shown in the specification:
say you had a component with a shadow tree like the following:
<x-foo class="foo">
<"shadow tree">
<div class="foo">...</div>
</>
</x-foo>
For a stylesheet within the shadow tree:
:host matches the <x-foo> element.
x-foo matches nothing.
.foo matches only the element.
.foo:host matches nothing
:host(.foo) matches the element.
Note the (2) and the (4). (2) is selecting nothing because no common selector can select outside the shadow tree. Only :host and :host() can do. The (4) is selecting nothing because :host is designed to be used alone to select the shadow host but if you want to add another selector you have to use :host() like in the (5).
Here is a basic example to illustrate:
class CustomElement extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'closed' });
const css = `
:host.box { color:red; }
`;
this.styles = document.createElement('style');
this.styles.innerHTML = css;
}
connectedCallback() {
const div = document.createElement('div');
div.innerHTML = `<code><${this.tagName.toLowerCase()}></code> connected!`;
this.shadow.appendChild(this.styles);
this.shadow.appendChild(div);
}
}
class OtherCustomElement extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'closed' });
const css = `
:host(.box) { color:red }
`;
this.styles = document.createElement('style');
this.styles.innerHTML = css;
}
connectedCallback() {
const div = document.createElement('div');
div.innerHTML = `<code><${this.tagName.toLowerCase()}></code> connected!`;
this.shadow.appendChild(this.styles);
this.shadow.appendChild(div);
}
}
customElements.define('custom-element', CustomElement);
customElements.define('other-custom-element', OtherCustomElement);
<custom-element class="box"></custom-element>
<other-custom-element class="box"></other-custom-element>
Now the question is: Why we have two kind of selectors when we can simply have :host combined with any other selector.
It's to avoid confusion and ambiguity when parsing the selector since the shadow host can only be selected by a special selector. If we write :host.foo the browser will try to match the element with .foo and :host but it will be tricky because .foo can only match an element inside the shadow tree while :host can go outside so parsing the selector to find if yes or no we have :host inside in order to consider the remaining part of the selector to match the shadow host will be tedious.
Using :host() make it easy for the browser to parse the selector and :host is a particular case of :host() with no selector.
Note: This is different from the specificity of similar pseudo-classes, like :matches() or :not(), which only take the specificity of their argument. This is because :host is affirmatively selecting an element all by itself, like a "normal" pseudo-class; it takes a selector argument for syntactic reasons (we can’t say that :host.foo matches but .foo doesn’t), but is otherwise identical to just using :host followed by a selector.
Note the we can’t say that :host.foo matches but .foo doesn’t
Related
Hot to use style element in shadowRoot.adoptedStyleSheets?
<style id=style>
h1 { color: red }
</style>
<script>
customElements.define('test-elem', class extends HTMLElement {
constructor () {
super()
const shadow = this.attachShadow({ mode: 'closed' })
shadow.adoptedStyleSheets = [style.sheet]
shadow.append(template.content.cloneNode(true))
}
})
</script>
https://developers.google.com/web/updates/2019/02/constructable-stylesheets
You must create a Constructable Sheet with new CSSStyleSheet()
You can only attach content on an 'open' shadowRoot
<style id="sourcestyle">
h1 {
color: green
}
</style>
<my-element></my-element>
<script>
customElements.define('my-element', class extends HTMLElement {
constructor() {
const sheet = new CSSStyleSheet();
sheet.replace(document.getElementById("sourcestyle").innerHTML);
super() // returns this/element scope
.attachShadow({ mode: 'open' }) // both sets and returns this.shadowRoot
.innerHTML = `<h1>Hello World!</h1>`;
this.shadowRoot.adoptedStyleSheets = [sheet];
}
})
</script>
Maybe better to construct that re-usable stylesheet outside your component,
and without creating a global <style>
const sheet = new CSSStyleSheet();
sheet.replace(`h1{color:green}`);
Without Constructable StyleSheet
(Constructable CSS doesn't work in all Browsers yet)
otherwise you might as well move the <style> with:
(or cloneNode it when you want to re-use it)
this.shadowRoot.append(document.getElementById("sourcestyle"));
Since it is a global sheet, you may want to disable it for the main DOM
<style id="sourcestyle" onload="this.disabled=true">
h1 {
color: green
}
</style>
and then undo the disabled once it is in a shadowDOM:
this.shadowRoot.getElementById("sourcestyle").disabled=false;
I'm using webpack and try to use css modules for my theming, for example:
<style lang="scss" module="theme1">
.header {
color: red;
}
</style>
<style lang="scss" module="theme2">
.header {
color: blue;
}
</style>
However, both .header tags get the same localIdentName from css-loader and because of that the second theme overrides the other everytime.
My loader chain: vue-loader, css-loader, sass-loader
My current localIdentName: '[local]_[hash:base64:5]' (neither path nor name result in anything, I just wish there was some sort of [value] tag.
Apparently it's because custom inject names were broken during the v14 -> v15 upgrade from vue-loader.
Heres the issue on GitHub with more details:
https://github.com/vuejs/vue-loader/issues/1578
Temporary solution (put this in the module options of css-loader):
{
localIdentName: '[module]_[local]_[hash:base64:5]',
getLocalIdent(context, localIdentName, localName) {
const { resourceQuery, resourcePath } = context;
const { index, module } = loaderUtils.parseQuery(resourceQuery);
const selector = loaderUtils.interpolateName(context, localIdentName, {
context: context.rootContext,
content: resourcePath + resourceQuery + localName,
})
.replace(/\[local\]/gi, localName)
.replace(/\[module\]/gi, typeof module === 'boolean' ? '' : module)
.replace(/\[index\]/gi, index)
.replace(new RegExp('[^a-zA-Z0-9\\-_\u00A0-\uFFFF]', 'g'), '-')
.replace(/^((-?[0-9])|--)/, '_$1');
return selector;
},
}
How to decrease the size of the mouse pointer in testcafe.. Below is code which I wrote but not working...
import { ClientFunction } from 'testcafe';
import { Selector } from 'testcafe';
fixture test
.page http://example.com
.beforeEach(async t => {
await disableCursor();
})
const disableCursor = ClientFunction(() => {
var styleElement = document.createElement('style');
styleElement.innerHTML = '.cursor-hammerhead-shadow-ui {width:10px; height:40px }';
document.head.appendChild(styleElement);
});
test('test', async t => {
await t.click(Selector('body > div > p:nth-child(3) > a'))
await t.click(Selector('#header > div.navigation > ul > li:nth-child(1) > a'))
});
To decrease the mouse pointer size, do the following:
Specify cursor selector: #root-hammerhead-shadow-ui.root-hammerhead-shadow-ui .cursor-hammerhead-shadow-ui.
Use the !important flag in CSS properties.
As a result, the code below replaces the default cursor with a red square:
const resizeCursor = ClientFunction(() => {
var styleElement = document.createElement('style');
styleElement.innerHTML = '#root-hammerhead-shadow-ui.root-hammerhead-shadow-ui .cursor-hammerhead-shadow-ui { background: red !important; width:40px !important; height:40px !important }';
document.head.appendChild(styleElement);
});
If you want to change the cursor to your own image, try this CSS property: background-image.
Note, since in a general case, it is hard to predict what unexpected results adding this CSS may produce, I suggest that you carefully check this prior to integrating this solution into your project.
See also: ClientFunction
I am trying to get my head around a scenario with CSS components:
I have a react component that uses its own classes. This component has a little helper subcomponent that also has its own classes. Now: When a specific state in the main component is set and a specific class is applied then the helper component's css should react on that class.
For instance:
Component A uses Component B to show something.
Component A gets clicked on and react sets a "clicked"-class on that component
Component B should then visually react on that class
In plain CSS (or similar) I would do this:
Component A:
.component {
height: 10px;
}
.component.clicked {
height: 5px;
}
Component B
.clicked {
.subComponent {
background-color: orange;
}
}
I know that there is a react way to do this. This kind of thing should be done with states and props which are being passed between the components so that this kind of situation gets avoided altogether. But I am currently refacturing a project that still has these issues and I don't really get how to do this properly with react-css-modules.
By the way: My current workaround uses :global but I'd really, really like to avoid this...
Component B:
.clicked:onclick, .subComponent {
// code ...
}
This should do it.
If not I'm just bad at css, or confused about your question.
Parent:
var ComponentA = React.createClass({
getInitialState: function() {
return {
isClicked: false
}
},
onClick: function() {
this.setState({ isClicked: !this.state.isClicked });
}),
render() {
return (
<div className={this.state.isClicked ? "component clicked" : "component"}>
<ComponentB isClicked={this.state.isClicked}/>
</div>
);
}
});
Child:
var ComponentB = React.createClass({
getDefaultProps: function() {
return {
isClicked: false
}
},
render() {
return (
<div className={this.props.isClicked ? "subComponent clicked" : "subComponent"}>
I am the subComponent
</div>
);
}
});
I have a directive for a table with collapsible rows that only allows one row to be open at a time like this:
HTML:
<div class="my-table">
<div class="table-header">
... table headers ...
</div>
<my-table-row ng-repeat="itm in itms" itm="itm"></my-table-row>
</div>
JS Directive:
app.directive('myTable', function() {
return {
restrict: 'E',
scope: {
itms: '='
},
controller: 'TableController',
templateUrl: '/views/directives/my-table.html'
};
});
JS Controller:
app.controller('TableController', ['$scope', function($scope) {
$scope.rows = [];
$scope.toggleRow = function(row) {
row.open = !row.open;
};
this.addRow = function addRow(row) {
$scope.rows.push(row);
};
this.toggleOpen = function toggleOpen(selectedRow) {
angular.forEach($scope.rows, function(row) {
if (row === selectedRow) {
$scope.toggleRow(selectedRow);
} else {
row.open = false;
}
});
};
}]);
and the rows like this:
HTML:
<div class="table-row" ng-class="{ 'open': open }" ng-click="toggleOpen(this)">
... row contents code ...
</div>
JS Directive:
app.directive('myTableRow', function() {
return {
require: '^myTable',
restrict: 'E',
scope: {
itm: '='
},
link: function(scope, element, attrs, tableCtrl) {
scope.open = false;
scope.toggleOpen = tableCtrl.toggleOpen;
tableCtrl.addRow(scope);
},
templateUrl: '/views/directives/my-table-row.html'
};
});
used in template like this:
<my-table itms="itms"></my-table>
This all works, but I have a CSS pseudo element to round the corners of the final row like:
.table .table-row:last-child {
border-radius: 0 0 4px 4px;
}
However, ng-repeat is wrapping a tag around my table rows which is causing the pseudo selector to see them all as the last child. I've tried restructuring, tried using $last and making an actual class for the last row, moving things around, but I'm out of ideas. Any thoughts out there?
as I understood, css class table-row is located within myTableRow directive, which does not have replace: true property. This means that table-row css class is wrapped by my-table-row directive attribute, so, in order to get to the last row, your CSS rule should be:
.table my-table-row:last-child .table-row {
border-radius: 0 0 4px 4px;
}