draft
Before Width: | Height: | Size: 324 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 735 KiB After Width: | Height: | Size: 15 KiB |
|
@ -181,11 +181,11 @@ They exist for historical reasons, to get either a string or XML document. Nowad
|
||||||
All states, as in [the specification](http://www.w3.org/TR/XMLHttpRequest/#states):
|
All states, as in [the specification](http://www.w3.org/TR/XMLHttpRequest/#states):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const unsigned short UNSENT = 0; // initial state
|
UNSENT = 0; // initial state
|
||||||
const unsigned short OPENED = 1; // open called
|
OPENED = 1; // open called
|
||||||
const unsigned short HEADERS_RECEIVED = 2; // response headers received
|
HEADERS_RECEIVED = 2; // response headers received
|
||||||
const unsigned short LOADING = 3; // response is loading (a data packed is received)
|
LOADING = 3; // response is loading (a data packed is received)
|
||||||
const unsigned short DONE = 4; // request complete
|
DONE = 4; // request complete
|
||||||
```
|
```
|
||||||
|
|
||||||
An `XMLHttpRequest` object travels them in the order `0` -> `1` -> `2` -> `3` -> ... -> `3` -> `4`. State `3` repeats every time a data packet is received over the network.
|
An `XMLHttpRequest` object travels them in the order `0` -> `1` -> `2` -> `3` -> ... -> `3` -> `4`. State `3` repeats every time a data packet is received over the network.
|
||||||
|
|
76
8-web-components/1-webcomponents-intro/article.md
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
# From the orbital height
|
||||||
|
|
||||||
|
This section describes a set of modern standards for "web components".
|
||||||
|
|
||||||
|
As of now, these standards are under development. Some features are well-supported and integrated into the modern HTML/DOM standard, while others are yet in draft stage. You can try examples in any browser, Google Chrome is probably the most up to date with these features. Guess, that's because Google fellows are behind many of the related specifications.
|
||||||
|
|
||||||
|
The whole component idea is nothing new. It's used in many frameworks and elsewhere.
|
||||||
|
|
||||||
|
## What's common between...
|
||||||
|
|
||||||
|
Before we move to implementation details, take a look at this great achievement of humanity:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
That's the International Space Station (ISS).
|
||||||
|
|
||||||
|
And this is how it's made inside (approximately):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The International Space Station:
|
||||||
|
- Consists of many components.
|
||||||
|
- Each component, in its turn, has many smaller details inside.
|
||||||
|
- The components are very complex, much more complicated than most websites.
|
||||||
|
- Components are developed internationally, by teams from different countries, speaking different languages.
|
||||||
|
|
||||||
|
...And this thing is flying, keeping humans alive in space!
|
||||||
|
|
||||||
|
How such complex devices are created?
|
||||||
|
|
||||||
|
Which principles we could borrow, to make our development same-level reliable and scalable? Or, at least, close to it.
|
||||||
|
|
||||||
|
## Component architecture
|
||||||
|
|
||||||
|
The well known rule for developing complex software is: don't make complex software.
|
||||||
|
|
||||||
|
If something becomes complex -- split it into simpler parts and connect in the most obvious way.
|
||||||
|
|
||||||
|
**A good architect is the one who can make the complex simple.**
|
||||||
|
|
||||||
|
We can split a user interface into components -- visual entities, each of them has own place on the page, can "do" a well-described task, and is separate from the others.
|
||||||
|
|
||||||
|
Let's take a look at a website, for example Twitter.
|
||||||
|
|
||||||
|
It naturally splits into components:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Top navigation.
|
||||||
|
2. User info.
|
||||||
|
3. Follow suggestions.
|
||||||
|
4. Submit form.
|
||||||
|
5. And also 6, 7 - messages.
|
||||||
|
|
||||||
|
Components may have subcomponents, e.g. messages may be parts of a higher-level "message list" component. A clickable user picture itself may be a component, and so on.
|
||||||
|
|
||||||
|
How do we decide, what is a component? That comes from intuition, experience and common sense. In the case above, the page has blocks, each of them plays its own role.
|
||||||
|
|
||||||
|
So, what comprises a component?
|
||||||
|
|
||||||
|
- A component has its own JavaScript class.
|
||||||
|
- DOM structure, managed solely by its class, outside code doesn't access it ("encapsulation" principle).
|
||||||
|
- CSS styles, applied to the component.
|
||||||
|
- API: events, class methods etc, to interact with other components.
|
||||||
|
|
||||||
|
"Web components" provide built-in browser capabilities for components:
|
||||||
|
|
||||||
|
- [Custom elements](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements) -- to define custom HTML elements.
|
||||||
|
- [Shadow DOM](https://dom.spec.whatwg.org/#shadow-trees) -- to create an internal DOM for the component, hidden from the others.
|
||||||
|
- [CSS Scoping](https://drafts.csswg.org/css-scoping/) -- to declare styles that only apply inside the component.
|
||||||
|
|
||||||
|
There exist many frameworks and development methodologies that aim to do the similar thing, each one with its own bells and whistles. Usually, special CSS classes and conventions are used to provide "component feel" -- CSS scoping and DOM encapsulation.
|
||||||
|
|
||||||
|
Web components provide built-in browser capabilities for that, so we don't have to emulate them any more.
|
||||||
|
|
||||||
|
In the next chapter we'll go into details of "Custom Elements" -- the fundamental and well-supported feature of web components, good on its own.
|
BIN
8-web-components/1-webcomponents-intro/satellite-expanded.jpg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
8-web-components/1-webcomponents-intro/satellite-expanded@2x.jpg
Normal file
After Width: | Height: | Size: 141 KiB |
BIN
8-web-components/1-webcomponents-intro/satellite.jpg
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
8-web-components/1-webcomponents-intro/satellite@2x.jpg
Normal file
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 133 KiB |
After Width: | Height: | Size: 278 KiB |
|
@ -0,0 +1,32 @@
|
||||||
|
class LiveTimer extends HTMLElement {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.innerHTML = `
|
||||||
|
<time-formatted hour="numeric" minute="numeric" second="numeric">
|
||||||
|
</time-formatted>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.timerElem = this.firstElementChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() { // (2)
|
||||||
|
if (!this.rendered) {
|
||||||
|
this.render();
|
||||||
|
this.rendered = true;
|
||||||
|
}
|
||||||
|
this.timer = setInterval(() => this.update(), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.date = new Date();
|
||||||
|
this.timerElem.setAttribute('datetime', this.date);
|
||||||
|
this.dispatchEvent(new CustomEvent('tick', { detail: this.date }));
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
clearInterval(this.timer); // important to let the element be garbage-collected
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("live-timer", LiveTimer);
|
|
@ -0,0 +1,34 @@
|
||||||
|
class TimeFormatted extends HTMLElement {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let date = new Date(this.getAttribute('datetime') || Date.now());
|
||||||
|
|
||||||
|
this.innerHTML = new Intl.DateTimeFormat("default", {
|
||||||
|
year: this.getAttribute('year') || undefined,
|
||||||
|
month: this.getAttribute('month') || undefined,
|
||||||
|
day: this.getAttribute('day') || undefined,
|
||||||
|
hour: this.getAttribute('hour') || undefined,
|
||||||
|
minute: this.getAttribute('minute') || undefined,
|
||||||
|
second: this.getAttribute('second') || undefined,
|
||||||
|
timeZoneName: this.getAttribute('time-zone-name') || undefined,
|
||||||
|
}).format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() { // (2)
|
||||||
|
if (!this.rendered) {
|
||||||
|
this.render();
|
||||||
|
this.rendered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get observedAttributes() { // (3)
|
||||||
|
return ['datetime', 'year', 'month', 'day', 'hour', 'minute', 'second', 'time-zone-name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback(name, oldValue, newValue) { // (4)
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("time-formatted", TimeFormatted);
|
|
@ -1,8 +1,266 @@
|
||||||
# Custom Elements
|
|
||||||
|
|
||||||
"Custom Elements" is a way to create new HTML elements and describe their properties, methods and constructor with JavaScript.
|
# Custom elements
|
||||||
|
|
||||||
After we define a custom element, we can use it freely both in scripts or in generated HTML, on par with built-in HTML elements. The browser becomes responsible for calling proper methods when the element is created, added or removed from DOM, and that's very convenient.
|
We can create our own class for a custom HTML element with its own methods and properties, events and so on.
|
||||||
|
|
||||||
|
There are two kinds of custom elements:
|
||||||
|
|
||||||
|
1. **Autonomous custom elements** -- "all-new" elements, extending the abstract `HTMLElement` class.
|
||||||
|
2. **Customized built-in elements** -- extending built-in elements, like customized `HTMLButtonElement` etc.
|
||||||
|
|
||||||
|
First we'll see how autonomous elements are made, and then the customized built-in ones.
|
||||||
|
|
||||||
|
For a class to describe an element, it should support so-called "custom element reactions" -- methods that the browser calls when our element is created/added/removed from DOM.
|
||||||
|
|
||||||
|
That's easy, as there are only few of them. Here's a sketch with the full list:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class MyElement extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
// element created
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
// browser calls it when the element is added to the document
|
||||||
|
// (can be called many times if an element is repeatedly added/removed)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
// browser calls it when the element is removed from the document
|
||||||
|
// (can be called many times if an element is repeatedly added/removed)
|
||||||
|
}
|
||||||
|
|
||||||
|
static get observedAttributes() {
|
||||||
|
return [/* array of attribute names to monitor for changes */];
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback(name, oldValue, newValue) {
|
||||||
|
// called when one of attributes listed above is modified
|
||||||
|
}
|
||||||
|
|
||||||
|
adoptedCallback() {
|
||||||
|
// called when the element is moved to a new document
|
||||||
|
// (document.adoptNode call, very rarely used)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then we need to register the element:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// let the browser know that <my-element> is served by our new class
|
||||||
|
customElements.define("my-element", MyElement);
|
||||||
|
```
|
||||||
|
|
||||||
|
Now for any new elements with tag `my-element`, an instance of `MyElement` is created, and the aforementioned methods are called.
|
||||||
|
|
||||||
|
```smart header="Custom element name must contain a hyphen `-`"
|
||||||
|
Custom element name must have a hyphen `-`, e.g. `my-element` and `super-button` are valid names, but `myelement` is not.
|
||||||
|
|
||||||
|
That's to ensure that there are no name conflicts between built-in and custom HTML elements.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: "time-formatted"
|
||||||
|
|
||||||
|
For example, there already exists `<time>` element in HTML, for date/time. But it doesn't do any formatting by itself.
|
||||||
|
|
||||||
|
Let's create `<time-formatted>` element that does the formatting:
|
||||||
|
|
||||||
|
|
||||||
|
```html run height=50 autorun
|
||||||
|
<script>
|
||||||
|
class TimeFormatted extends HTMLElement {
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
let date = new Date(this.getAttribute('datetime') || Date.now());
|
||||||
|
|
||||||
|
this.innerHTML = new Intl.DateTimeFormat("default", {
|
||||||
|
year: this.getAttribute('year') || undefined,
|
||||||
|
month: this.getAttribute('month') || undefined,
|
||||||
|
day: this.getAttribute('day') || undefined,
|
||||||
|
hour: this.getAttribute('hour') || undefined,
|
||||||
|
minute: this.getAttribute('minute') || undefined,
|
||||||
|
second: this.getAttribute('second') || undefined,
|
||||||
|
timeZoneName: this.getAttribute('time-zone-name') || undefined,
|
||||||
|
}).format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("time-formatted", TimeFormatted);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<time-formatted
|
||||||
|
datetime="2019-12-01"
|
||||||
|
year="numeric" month="long" day="numeric"
|
||||||
|
hour="numeric" minute="numeric" second="numeric"
|
||||||
|
time-zone-name="short"
|
||||||
|
></time-formatted>
|
||||||
|
```
|
||||||
|
|
||||||
|
As the result, `<time-formatted>` shows a nicely formatted time, according to the browser timezone and locale. We use the built-in [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat) data formatter, well-supported across the browsers.
|
||||||
|
|
||||||
|
```smart header="Custom elements upgrade"
|
||||||
|
If the browser encounters any `<time-formatted>` elements before `customElements.define` call, they are yet unknown, just like any non-standard tag.
|
||||||
|
|
||||||
|
They can be styled with CSS selector `:not(:defined)`.
|
||||||
|
|
||||||
|
When `customElement.define` is called, they are "upgraded": a new instance of `TimeFormatted`
|
||||||
|
is created for each, and `connectedCallback` is called. They become `:defined`.
|
||||||
|
|
||||||
|
To track custom elements from JavaScript, there are methods:
|
||||||
|
- `customElements.get(name)` -- returns the class for a defined custom element with the given `name`,
|
||||||
|
- `customElements.whenDefined(name)` -- returns a promise that resolves (without value) when a custom element with the given `name` is defined.
|
||||||
|
```
|
||||||
|
|
||||||
|
```smart header="Rendering in `connectedCallback` instead of `constructor`"
|
||||||
|
In the example above, element content is rendered (created) in `connectedCallback`.
|
||||||
|
|
||||||
|
Why not in the `constructor`?
|
||||||
|
|
||||||
|
First, that might be better performance-wise -- to delay the work until its really needed.
|
||||||
|
|
||||||
|
The `connectedCallback` triggers when the element is in the document, not just appended to another element as a child. So we can build detached DOM, create elements and prepare them for later use. They will only be actually rendered when they make it into the page.
|
||||||
|
|
||||||
|
Second, when the element is on the page, we can get geometry information about it, e.g. sizes (`elem.offsetWidth/offsetHeight`), so rendering at this stage is more powerful.
|
||||||
|
|
||||||
|
On the other hand, `constructor` is the right place to initialize internal data structures, do lightweight jobs.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Observing attributes
|
||||||
|
|
||||||
|
Please note that in the current implementation, after the element is rendered, further attribute changes don't have any effect. That's strange for an HTML element. Usually, when we change an attribute, like `a.href`, the change is immediately visible. So let's fix this.
|
||||||
|
|
||||||
|
We can observe attributes by providing their list in `observedAttributes()` static getter, and then update the element in `attributeChangedCallback`. It's called for changes only in the listed attributes for performance reasons.
|
||||||
|
|
||||||
|
```html run autorun height=50
|
||||||
|
<script>
|
||||||
|
class TimeFormatted extends HTMLElement {
|
||||||
|
|
||||||
|
*!*
|
||||||
|
render() { // (1)
|
||||||
|
*/!*
|
||||||
|
let date = new Date(this.getAttribute('datetime') || Date.now());
|
||||||
|
|
||||||
|
this.innerHTML = new Intl.DateTimeFormat("default", {
|
||||||
|
year: this.getAttribute('year') || undefined,
|
||||||
|
month: this.getAttribute('month') || undefined,
|
||||||
|
day: this.getAttribute('day') || undefined,
|
||||||
|
hour: this.getAttribute('hour') || undefined,
|
||||||
|
minute: this.getAttribute('minute') || undefined,
|
||||||
|
second: this.getAttribute('second') || undefined,
|
||||||
|
timeZoneName: this.getAttribute('time-zone-name') || undefined,
|
||||||
|
}).format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
connectedCallback() { // (2)
|
||||||
|
*/!*
|
||||||
|
if (!this.rendered) {
|
||||||
|
this.render();
|
||||||
|
this.rendered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
static get observedAttributes() { // (3)
|
||||||
|
*/!*
|
||||||
|
return ['datetime', 'year', 'month', 'day', 'hour', 'minute', 'second', 'time-zone-name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
attributeChangedCallback(name, oldValue, newValue) { // (4)
|
||||||
|
*/!*
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("time-formatted", TimeFormatted);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<time-formatted id="elem" hour="numeric" minute="numeric" second="numeric"></time-formatted>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
*!*
|
||||||
|
setInterval(() => elem.setAttribute('datetime', new Date()), 1000); // (5)
|
||||||
|
*/!*
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
1. The rendering logic is moved to `render()` helper method.
|
||||||
|
2. We call it once when the element is inserted into page.
|
||||||
|
3. For a change of an attribute, listed in `observedAttributes()`, `attributeChangedCallback` triggers.
|
||||||
|
4. ...and re-renders the element.
|
||||||
|
5. At the end, we can easily make a live timer.
|
||||||
|
|
||||||
|
|
||||||
|
## Customized built-in elements
|
||||||
|
|
||||||
|
New custom elements like `<time-formatted>` don't have any associated semantics. They are totally new to search engines and accessibility devices.
|
||||||
|
|
||||||
|
We could use special [ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) attributes to describe the semantic. But if we're going to make a special button, why not extend a `<button>` element itself?
|
||||||
|
|
||||||
|
Built-in elements can be customized by inheriting from their classes. HTML buttons are instances of `HTMLButtonElement`, so let's extend it:
|
||||||
|
|
||||||
|
```html run autorun
|
||||||
|
<script>
|
||||||
|
// The button that says "hello" on click
|
||||||
|
class HelloButton extends HTMLButtonElement {
|
||||||
|
*!*
|
||||||
|
constructor() { // (1)
|
||||||
|
*/!*
|
||||||
|
super();
|
||||||
|
this.addEventListener('click', () => alert("Hello!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
customElements.define('hello-button', HelloButton, {extends: 'button'}); // 2
|
||||||
|
*/!*
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- 3 -->
|
||||||
|
*!*
|
||||||
|
<button is="hello-button">Click me</button>
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
<!-- 4 -->
|
||||||
|
*!*
|
||||||
|
<button is="hello-button" disabled>Disabled</button>
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
1. We constructor add an event listener to the element. Please note: we must call `super()` before anything else (that's pure JS requirement).
|
||||||
|
2. To extend a built-in element, we must specify `{extends: '<tag>'}` in the define. Some tags share the same HTML class, so we need to be precise here.
|
||||||
|
3. Now we can use a regular `<button>` tag, labelled with `is="hello-button"`.
|
||||||
|
4. Our buttons extend built-in ones, so they retain the standard features like `disabled` attribute.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
For example, we can create `<custom-dropdown>` -- a nice-looking dropdown select, `<phone-input>` -- an input element for a phone number, and other graphical components.
|
||||||
|
|
||||||
|
There are two kinds of custom elements:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
An autonomous custom element, which is defined with no extends option. These types of custom elements have a local name equal to their defined name.
|
||||||
|
|
||||||
|
A customized built-in element, which is defined with an extends option. These types of custom elements have a local name equal to the value passed in their extends option, and their defined name is used as the value of the is attribute, which therefore must be a valid custom element name.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```js run
|
||||||
|
class DateLocal extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
// element is created/upgraded
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
38
8-web-components/2-custom-elements/head.html
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<script>
|
||||||
|
/*
|
||||||
|
class TimeFormatted extends HTMLElement {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let date = new Date(this.getAttribute('datetime') || Date.now());
|
||||||
|
|
||||||
|
this.innerHTML = new Intl.DateTimeFormat("default", {
|
||||||
|
year: this.getAttribute('year') || undefined,
|
||||||
|
month: this.getAttribute('month') || undefined,
|
||||||
|
day: this.getAttribute('day') || undefined,
|
||||||
|
hour: this.getAttribute('hour') || undefined,
|
||||||
|
minute: this.getAttribute('minute') || undefined,
|
||||||
|
second: this.getAttribute('second') || undefined,
|
||||||
|
timeZoneName: this.getAttribute('time-zone-name') || undefined,
|
||||||
|
}).format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() { // (2)
|
||||||
|
if (!this.rendered) {
|
||||||
|
this.render();
|
||||||
|
this.rendered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get observedAttributes() { // (3)
|
||||||
|
return ['datetime', 'year', 'month', 'day', 'hour', 'minute', 'second', 'time-zone-name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback(name, oldValue, newValue) { // (4)
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
window.customElements && customElements.define("time-formatted", TimeFormatted);
|
||||||
|
*/
|
||||||
|
</script>
|
Before Width: | Height: | Size: 6 KiB After Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
@ -1,3 +1,3 @@
|
||||||
# Web components
|
# Web components
|
||||||
|
|
||||||
Web components is a set of standards to make self-contained components: custom HTML-elements with their own properties and methods, incapsulated DOM and styles.
|
Web components is a set of standards to make self-contained components: custom HTML-elements with their own properties and methods, encapsulated DOM and styles.
|