first commit

This commit is contained in:
2026-08-04 15:32:57 +06:30
commit be19f89c74
26 changed files with 3669 additions and 0 deletions

896
ShadowDOMAndStyling.md Executable file
View File

@@ -0,0 +1,896 @@
# Shadow DOM concepts
You can add a shadow tree to an element imperatively by calling attachShadow:
var div = document.createElement('div');
var shadowRoot = div.attachShadow({mode: 'open'});
shadowRoot.innerHTML ='<h1>Hello Shadow DOM</h1>';
## Shadow DOM and composition
By default, if an element has shadow DOM, the shadow tree is rendered instead of the element's children. To allow children to render, you can add a **slot** element to your shadow tree.
Consider the following shadow tree for my-header:
<header>
<h1><slot></slot></h1>
<button>Menu</button>
</header>
The user can add children like this:
<my-header>Shadow DOM</my-header>
The header renders as if the <slot> element was replaced by the children:
<my-header>
<header>
<h1>Shadow DOM</h1>
<button>Menu</button>
</header>
</my-header>
While the <slot> elements don't render, they are included in the flattened tree, so they can take part in event bubbling, for example.
You can control where a child should be distributed into the flattened tree using named slots.
<h2><slot name="title"></slot></h2>
<div><slot></slot></div>
A named slot only accepts top-level children that have a matching slot attribute:
<span slot="title">A heading</span>
## Fallback content
A slot can contain fallback content that's displayed when no nodes are assigned to the slot. For example:
<!-- shows note with warning icon -->
<fancy-note>
<img slot="icon" src="warning.png">
Do not operate heavy equipment while coding.
</fancy-note>
If the user omits the icon, the fallback content supplies a default icon:
<!-- shows note with default icon -->
<fancy-note>
Please code responsibly.
</fancy-note>
## Slot APIs
Shadow DOM provides a few new APIs for checking distribution:
-**HTMLElement.assignedSlot** property gives the assigned slot for an element, or null if the element isn't assigned to a slot.
-**HTMLSlotElement.assignedNodes** method returns the list of nodes associated with a given slot. When called with the {flatten: true} option, returns the distributed nodes for a slot.
-**HTMLSlotElement.slotchange** event is fired when a slot's distributed nodes change.
## Observe added and removed children
Use the FlattenedNodesObserver class to track when the flattened node list changes.
import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
...
this._observer = new FlattenedNodesObserver(this.$.slot, (info) => {
this._processNewNodes(info.addedNodes);
this._processRemovedNodes(info.removedNodes);
});
## Event retargeting
Retargeting adjusts the event's target so that it represents an element in the same scope as the listening element.
For example, given a tree like this:
<example-card>
#shadow-root
<div>
<fancy-button>
#shadow-root
<img>
If the user clicks on the image element the click event bubbles up the tree:
* A listener on the image element itself receives the **img** as the target.
* A listener on the **fancy-button** receives the **fancy-button** as the target, because the original target is inside its shadow root.
* A listener on the **div** in **example-card**'s shadow DOM also receives **fancy-button** as the target, since they are in the same shadow DOM tree.
* A listener on the **example-card** receives the **example-card** itself as the target.
The event provides a **composedPath** method that returns an array of nodes that the event will pass through.
To allow a custom event to travel through a shadow DOM boundary and be retargeted, you need to create it with the **composed** flag set to **true**:
var event = new CustomEvent('my-event', {bubbles: true, composed: true});
## Shadow DOM styling
Styles inside a shadow tree are scoped to the shadow tree, and don't affect elements outside the shadow tree. Styles outside the shadow tree also don't match selectors inside the shadow tree.
There is one case where a style rule inside a shadow tree matches an element outside the shadow tree. You can define styles for the host element, using the **:host** pseudoclass or the **:host()** functional pseudoclass.
#shadow-root
<style>
/* custom elements default to display: inline */
:host {
display: block;
}
/* set a special background when the host element
has the .warning class */
:host(.warning) {
background-color: red;
}
</style>
You can also style light DOM children that are assigned to slots using the **::slotted()** pseudoelement. For example,
#shadow-root
<style>
::slotted(img) {
border-radius: 100%;
}
</style>
## Theming and custom properties
html {
--my-theme-color: red;
}
The substitution can include default values to use if no property is set:
:host {
background-color: var(--my-theme-color, blue);
}
The default can even be another var() function:
background-color: var(--my-theme-color, var(--another-theme-color, blue));
#### Custom property mixins
Custom property mixins are a feature built on top of the custom property specification.
html {
--my-custom-mixin: {
color: white;
background-color: blue;
}
}
A component can import or mix in the entire set of rules using the @apply rule:
:host {
@apply --my-custom-mixin;
}
## Shadow DOM polyfills
The polyfills use a combination of techniques to emulate shadow DOM:
* Shady DOM. Maintains the logical divisions of shadow tree and descendant tree internally, so children added to the light DOM or shadow DOM render correctly. Patches DOM APIs on affected elements in order to emulate the native shadow DOM APIs.
* Shady CSS. Provides style encapsulation by adding classes to shadow DOM children and rewriting style rules so that they apply to the correct scope.
# Specify a DOM template
1.Define a template property on the constructor
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
class MyElement extends PolymerElement {
static get template() {
return html`<style>:host { color: blue; }</style>
<h2>String template</h2>
<div>This is my template!</div>`;
}
}
customElements.define('my-element', MyElement);
2.Inherit a template from another Polymer element
#### Inherit a base class template without modifying it
Base class definition:
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
export class BaseClass extends PolymerElement {
static get template() {
return html`This content has been inherited from BaseClass.`; }
}
customElements.define('base-class', BaseClass);
Child class definition:
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
import {BaseClass} from './base-class.js'
export class ChildClass extends BaseClass {
// ... no template defined, child inherits
// parent's template
}
customElements.define('child-class', ChildClass);
#### Override a base class template in a child class
Base class definition:
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
export class BaseClass extends PolymerElement {
static get template() {
return html`This content has been inherited from BaseClass.`;
}
}
customElements.define('base-class', BaseClass);
Child class definition:
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
import {BaseClass} from './base-class.js'
export class ChildClass extends BaseClass {
static get template() {
return html`Base class template has been overridden. Hello from ChildClass!`;
}
}
customElements.define('child-class', ChildClass);
#### Extend a base class template in a child class
To extend a base class template, include the base class template in your child class template literal with the expression **${super.template}**. You will also need to tag the template literal with the html function:
Base class definition:
class BaseClass extends PolymerElement {
static get template() {
return html`
<p>This content has been inherited from BaseClass.</p>`
}
}
customElements.define('base-class', BaseClass);
Child class definition:
class ChildClass extends BaseClass {
static get template() {
return html`
<p>This content is from ChildClass.</p>
<p>${super.template}</p>
<p>Hello again from ChildClass.</p>
`;
}
}
customElements.define('child-class', ChildClass);
#### Provide template extension points
You can provide template extension points by composing your base class template literal using expressions, like **${this.partialTemplate}**.
Base class definition:
export class BaseClass extends PolymerElement {
static get template() {
return html`
<div>${this.headerTemplate}</div>
<p>Hello this is some content</p>
<div>${this.footerTemplate}</div>
`;
}
static get headerTemplate() { return html`<h1>BaseClass: Header</h1>` }
static get footerTemplate() { return html`<h1>BaseClass: Footer</h1>` }
}
Child class definition:
export class ChildClass extends BaseClass {
// template definition inherited from BaseClass
// partial templates overridden by ChildClass
static get headerTemplate() { return html`<h2>ChildClass: Header</h2>` }
static get footerTemplate() { return html`<h2>ChildClass: Footer</h2>` }
}
## URLs in templates
To ensure URLs resolve properly, Polymer provides two properties that can be used in data bindings:
**importPath** -A static getter on the element class. To set URLs relative to the import, you must override the **importPath** getter.
For Example,
// This getter must be defined for your element if you want to use importPath
static get importPath() {
// return the base URL for this import
return import.meta.url;
}
static get template() {
return html`
<img src$="[[importPath]]checked.jpg">
`
}
**rootPath** -An instance property set to the value of **Polymer.rootPath** which is globally settable and defaults to the main document URL. It may be useful to set **Polymer.rootPath** to provide a stable application mount path when using client side routing.
<a href$="[[rootPath]]users/profile">View profile</a>
## Static node map
Any node specified in the element's template with an **id** is stored on the **this.$** hash by **id**.
Example:
class MyElement extends PolymerElement {
static get template() {
return html`Hello World from <span id="name"></span>!`;
}
ready() {
super.ready();
this.$.name.textContent = this.tagName;
}
}
## Remove empty text nodes
Add the **strip-whitespace** boolean attribute to a template to remove any empty text nodes from the template's contents.
<dom-module id="no-whitespace">
<template strip-whitespace>
<div>Some Text</div>
<div>More Text</div>
</template>
<script>
class NoWhitespace extends PolymerElement {
static get is() { return 'no-whitespace' }
ready() {
super.ready();
console.log(this.shadowRoot.childNodes.length); // 2
}
}
customElements.define(NoWhitespace.is, NoWhitespace);
</script>
</dom-module>
## Preserve template contents
If you want to access the contents of a nested template, you can add the **preserve-content** attribute to the template.
class CustomTemplate extends PolymerElement {
static get tempate() { return html`
<template id="special-template" preserve-content>
<div>I am very special.</div>
</template>`
}
ready() {
super.ready();
// retrieve the nested template
let template = this.shadowRoot.querySelector('#special-template');
//
for (let i=0; i<10; i++) {
this.shadowRoot.appendChild(document.importNode(template.content, true));
}
}
}
customElements.define(CustomTemplate.is, CustomTemplate);
## Create your own shadow root
You may want to create your own shadow root. You can do this by creating a shadow root before calling **super.ready()**—or before the **ready** callback.
You can also override the _attachDom method:
_attachDom(dom) {
this.attachShadow({mode: 'open', delegatesFocus: true});
super._attachDom(dom);
}
# Style an element's shadow DOM
## Style your elements
Shadow DOM permits encapsulation of styling rules for custom elements. You can freely define styling information for your elements, such as fonts, text colors, and classes, without fear of the styles applying outside the scope of your element.
custom-element.js
...
static get template() {
return html`
<!-- Encapsulated, element-level stylesheet -->
<style>
p {
color: green;
}
.myclass {
color: red;
}
</style>
<p>I'm a shadow DOM child element of <code>custom-element</code>.</p>
<p class="myclass">So am I.</p>
`;
}
...
index.html
<head>
<script type="module" src="custom-element.js"></script>
<!-- Document-level stylesheet -->
<style>
.myclass {
color: blue;
}
</style>
</head>
<body>
<custom-element></custom-element>
<p class="myclass">I am outside of <code>custom-element</code>. Because of encapsulation, <code>custom-element</code>'s styles won't leak to me.</p>
</body>
## Use inheritance from document-level styles
When used in an HTML document, your element will still inherit any styling information that applies to its parent element:
custom-element.js
...
static get template() {
return html`
<div>
I inherit styles from <code>custom-element</code>'s parent in the light DOM.
I'm also sans-serif and blue.
</div>
`;
}
...
index.html
<head>
<script type="module" src="custom-element.js"></script>
<!-- Document-level stylesheet -->
<style>
p {
font-family: sans-serif;
color:blue;
}
</style>
</head>
<body>
<!-- This paragraph uses document-level styles: -->
<p>I'm sans-serif and blue.</p>
<!-- And the text within custom-element inherits style from the paragraph element: -->
<p><custom-element></custom-element></p>
</body>
Styles declared inside shadow DOM will override styles declared outside of it:
custom-element.js
...
static get template() {
return html`
<!-- Encapsulated, element-level stylesheet
overrides document-level stylesheet -->
<style>
p {
font-family: sans-serif;
color: green;
}
</style>
<p>I'm green.</p>
`;
}
...
index.html
<head>
<script type="module" src="custom-element.js"></script>
<!-- Document-level stylesheet -->
<style>
p {
font-family: sans-serif;
color:blue;
}
</style>
</head>
<body>
<p>I'm blue.</p>
<p><custom-element></custom-element></p>
</body>
## Style the host element
The element to which shadow DOM is attached is known as the host. To style the host, use the :host selector.
You can use CSS selectors to determine when and how to style the host. In this code sample:
* The selector :host matches any <custom-element> element
* The selector :host(.blue) matches <custom-element> elements of class blue
* The selector :host(.red) matches <custom-element> elements of class red
* The selector :host(:hover) matches <custom-element> elements when they are hovered over
custom-element.js
...
static get template() {
return html`
<style>
:host { font-family: sans-serif; }
:host(.blue) {color: blue;}
:host(.red) {color: red;}
:host(:hover) {color: green;}
</style>
<p>Hi, from custom-element!</p>
`;
}
...
index.html
<head>
<script type="module" src="custom-element.js"></script>
</head>
<body>
<custom-element class="blue"></custom-element>
<custom-element class="red"></custom-element>
</body>
## Style slotted content (distributed children)
To style slotted content, use the ::slotted() syntax.
You can select by element type:
custom-element.js
...
static get template() {
return html`
<style>
h1 ::slotted(h1) {
font-family: sans-serif;
color: green;
}
p ::slotted(p) {
font-family: sans-serif;
color: blue;
}
</style>
<h1><slot name='heading1'></slot></h1>
<p><slot name='para'></slot></p>
`;
}
...
index.html
<head>
<script type="module" src="custom-element.js"></script>
</head>
<body>
<custom-element>
<h1 slot="heading1">Heading 1. I'm green.</h1>
<p slot="para">Paragraph text. I'm blue.</p>
</custom-element>
</body>
You can select by class:
custom-element.js
...
static get template() {
return html`
<style>
p ::slotted(.green) {
color:green;
}
</style>
<p>
<slot name='para1'></slot>
</p>
<p>
<slot name='para2'></slot>
</p>
`;
}
...
index.html
<head>
<script type="module" src="custom-element.js"></script>
</head>
<body>
<custom-element>
<div slot="para1" class="green">I'm green!</div>
<div slot="para1">I'm not green.</div>
<div slot="para2" class="green">I'm green too.</div>
<div slot="para2">I'm not green.</div>
</custom-element>
</body>
And you can select by slot name:
custom-element.js
...
static get template() {
return html`
<style>
p ::slotted([slot=para1]) {
color:green;
}
</style>
<p>
<slot name='para1'></slot>
</p>
<p>
<slot name='para2'></slot>
</p>
`;
}
...
index.html
<head>
<script type="module" src="custom-element.js"></script>
</head>
<body>
<custom-element>
<div slot="para1">I'm green.</div>
<div slot="para2">I'm not green.</div>
</custom-element>
</body>
## Style directional text with the :dir() selector
The :dir() CSS selector allows for styling text specific to its orientation (right-to-left or left-to-right).
The **DirMixin** provides limited support for the **:dir()** selector. Use of **dir()** requires the application to set the **dir** attribute on <html>. All elements will use the same direction.
For elements that extend **PolymerElement**, add **DirMixin** to use **:dir()** styling.For Example,
using-dir-selector.js
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';
import DirMixin from '@polymer/polymer/lib/mixins/dir-mixin.js';
class UsingDirSelector extends DirMixin(PolymerElement) {
static get template() {
return html`
<style>
:host {
display: block;
color: blue;
}
:host(:dir(rtl)) {
color: green;
}
</style>
...
`;
}
}
customElements.define('using-dir-selector', UsingDirSelector);
index.html
<html lang="en" dir="rtl">
<head>
<script type="module" src="./using-dir-selector.js"></script>
</head>
<body>
<using-dir-selector></using-dir-selector>
</body>
</html>
## Use style modules
To create a style module:
1.Use JavaScript to create a <dom-module> element:
const styleElement = document.createElement('dom-module');
2.Set the <dom-module> element's innerHTML property to contain a **template** element that wraps a **style** block:
styleElement.innerHTML =
`<template>
<style>
/* Your shared styles go here */
</style>
</template>`;
3.Register your style module as an element:
styleElement.register('style-element');
You'll most likely want to package the style module in its own JavaScript file. The element that uses the styles will need to import that file. For example:
import './style-element.js';
When you create the element that will use the styles, include the style module in the opening tag of the style block:
static get template() {
return html`
<style include="style-element">
<!-- Any additional styles go here -->
</style>
<!-- The rest of your element template goes here -->
`;
}
}
## Use custom-style in document-level styles
To ensure that your styles behave according to the Shadow DOM v1 specifications in all browsers, use custom-style when you define document-level styles
Import **custom-style** from **@polymer/polymer/lib/elements/custom-style.js**:
index.html
<head>
<script src="./custom-element.js" type="module">
<script src="../@polymer/polymer/lib/elements/custom-style.js" type="module">
<custom-style>
<style>
/* Document-level styles go here */
</style>
</custom-style>
</head>
<body>
<custom-element></custom-element>
</body>
# Use custom properties
## Use the custom CSS properties provided by a Polymer element
The author of a Polymer element can provide custom CSS properties that you can use to style the appearance of the element in your application.
For Example,
index.html
<html>
<head>
<script type="module" src="./flex-container.js">
<script type="module" src="./flex-item.js">
<!-- custom-style element invokes the custom properties polyfill -->
<script type="module" src="./node_modules/@polymer/polymer/lib/elements/custom-style.js"></script>
<!-- ensure that custom props are polyfilled on browsers that don't support them -->
<custom-style>
<style>
html {
/* Set a value for the custom CSS property --flex-direction */
--flex-direction: column
}
</style>
</custom-style>
</head>
<body>
<flex-container>
<flex-item>flex item 1</flex-item>
<!-- ... -->
<flex-item>flex item n</flex-item>
</flex-container>
</body>
</html>
## Provide custom CSS properties to users of your elements
For example,
flex-container.js (your code)
/* ... */
class FlexContainer extends PolymerElement {
static get template () {
return html`
<style>
:host {
display: flex;
flex-direction: var(--flex-direction);
}
</style>
<!-- ... -->
`;
}
}
/* ... */
Users can then assign their own value to --flex-direction like so:
index.html (user's code)
...
<style>
html {
--flex-direction: column
}
</style>
...
## Create default values for your CSS properties
To set a default value for a CSS property, use the following syntax:
div {
background-color: var(--theme-background, #e3f2fd);
}
## Inheritance and global styles
Custom CSS properties inherit down the DOM hierarchy. In the code sample below, <custom-element> will inherit the custom properties defined for div, but not the custom properties defined for span.
<html>
<head>
<!-- custom-style element invokes the custom properties polyfill -->
<script type="module" src="node_modules/@polymer/polymer/lib/elements/custom-style.js"></script>
<!-- ensure that custom props are polyfilled on browsers that don't support them -->
<custom-style>
<style>
div {
/* flex-container is a child of div and will inherit these */
--theme-dark-blue: #0d47a1;
--theme-light-blue: #e3f2fd;
color: var(--theme-dark-blue);
background-color: var(--theme-light-blue);
}
span {
/* flex-container is not a child of span and will not inherit these */
--theme-wide-padding: 24px;
--theme-font-family: Roboto, Noto, sans-serif;
padding: var(--theme-wide-padding);
font-family: var(--theme-font-family);
}
</style>
</custom-style>
</head>
<body>
<div>
<flex-container>
<flex-item>flex item 1</flex-item>
<flex-item>flex item 2</flex-item>
<flex-item>flex item 3</flex-item>
</flex-container>
</div>
<span>
<p>hello i am in a span</p>
</span>
</body>
</html>
## Use custom CSS mixins
Using CSS mixins, you can define a set of CSS properties as a single custom property.
selector {
--mixin-name: {
/* rules */
};
}
Use @apply to apply a mixin:
selector {
@apply --mixin-name;
}
Note that any element using the @apply syntax must import the **@apply** polyfill:
// import CSS mixins polyfill
import '@webcomponents/shadycss/entrypoints/apply-shim.js';
## Custom property API for Polymer elements
To update all elements on the page, you can also call Polymer.updateStyles.
**UpdateStyles** can take a object with property/value pairs to update the current values of custom properties.
Example
class XCustom extends PolymerElement {
static get changeTheme() {
return function() {
this.updateStyles({
'--my-toolbar-color': 'blue',
});
}
}
static template get (){
return html`
<style>
:host {
--my-toolbar-color: red;
}
</style>
<my-toolbar>My awesome app</my-toolbar>
<button on-tap="changeTheme">Change theme</button>
`;
}
}
customElements.define('x-custom', XCustom);
Occasionally an element needs to get the value of a custom property at runtime. This is handled slightly differently depending on whether the shady CSS polyfill is loaded:
if (ShadyCSS) {
style = ShadyCSS.getComputedStyleValue(this, '--something');
} else {
style = getComputedStyle(this).getPropertyValue('--something');
}
Elements using the legacy API can use the **getComputedStyleValue** instance method instead of testing for ShadyCSS.