mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
Custom components support
This commit is contained in:
@@ -11835,6 +11835,25 @@
|
||||
"client/ios/codegen_DivKit.sh":"divkit/public/client/ios/codegen_DivKit.sh",
|
||||
"client/ios/codegen_Tests.sh":"divkit/public/client/ios/codegen_Tests.sh",
|
||||
"client/web/divkit-examples/README.md":"divkit/public/client/web/divkit-examples/README.md",
|
||||
"client/web/divkit-examples/custom-container/.npmrc":"divkit/public/client/web/divkit-examples/custom-container/.npmrc",
|
||||
"client/web/divkit-examples/custom-container/README.md":"divkit/public/client/web/divkit-examples/custom-container/README.md",
|
||||
"client/web/divkit-examples/custom-container/customElement.js":"divkit/public/client/web/divkit-examples/custom-container/customElement.js",
|
||||
"client/web/divkit-examples/custom-container/index.html":"divkit/public/client/web/divkit-examples/custom-container/index.html",
|
||||
"client/web/divkit-examples/custom-container/package-lock.json":"divkit/public/client/web/divkit-examples/custom-container/package-lock.json",
|
||||
"client/web/divkit-examples/custom-container/package.json":"divkit/public/client/web/divkit-examples/custom-container/package.json",
|
||||
"client/web/divkit-examples/custom-simple/.npmrc":"divkit/public/client/web/divkit-examples/custom-simple/.npmrc",
|
||||
"client/web/divkit-examples/custom-simple/README.md":"divkit/public/client/web/divkit-examples/custom-simple/README.md",
|
||||
"client/web/divkit-examples/custom-simple/customElement.js":"divkit/public/client/web/divkit-examples/custom-simple/customElement.js",
|
||||
"client/web/divkit-examples/custom-simple/index.html":"divkit/public/client/web/divkit-examples/custom-simple/index.html",
|
||||
"client/web/divkit-examples/custom-simple/package-lock.json":"divkit/public/client/web/divkit-examples/custom-simple/package-lock.json",
|
||||
"client/web/divkit-examples/custom-simple/package.json":"divkit/public/client/web/divkit-examples/custom-simple/package.json",
|
||||
"client/web/divkit-examples/custom-ssr/.npmrc":"divkit/public/client/web/divkit-examples/custom-ssr/.npmrc",
|
||||
"client/web/divkit-examples/custom-ssr/README.md":"divkit/public/client/web/divkit-examples/custom-ssr/README.md",
|
||||
"client/web/divkit-examples/custom-ssr/customElement.js":"divkit/public/client/web/divkit-examples/custom-ssr/customElement.js",
|
||||
"client/web/divkit-examples/custom-ssr/package-lock.json":"divkit/public/client/web/divkit-examples/custom-ssr/package-lock.json",
|
||||
"client/web/divkit-examples/custom-ssr/package.json":"divkit/public/client/web/divkit-examples/custom-ssr/package.json",
|
||||
"client/web/divkit-examples/custom-ssr/server.js":"divkit/public/client/web/divkit-examples/custom-ssr/server.js",
|
||||
"client/web/divkit-examples/custom-ssr/template.html":"divkit/public/client/web/divkit-examples/custom-ssr/template.html",
|
||||
"client/web/divkit-examples/divkit-react/.npmrc":"divkit/public/client/web/divkit-examples/divkit-react/.npmrc",
|
||||
"client/web/divkit-examples/divkit-react/README.md":"divkit/public/client/web/divkit-examples/divkit-react/README.md",
|
||||
"client/web/divkit-examples/divkit-react/index.html":"divkit/public/client/web/divkit-examples/divkit-react/index.html",
|
||||
@@ -11925,6 +11944,7 @@
|
||||
"client/web/divkit/src/components/container/Container.svelte":"divkit/public/client/web/divkit/src/components/container/Container.svelte",
|
||||
"client/web/divkit/src/components/container/ContainerSeparator.module.css":"divkit/public/client/web/divkit/src/components/container/ContainerSeparator.module.css",
|
||||
"client/web/divkit/src/components/container/ContainerSeparators.svelte":"divkit/public/client/web/divkit/src/components/container/ContainerSeparators.svelte",
|
||||
"client/web/divkit/src/components/custom/Custom.svelte":"divkit/public/client/web/divkit/src/components/custom/Custom.svelte",
|
||||
"client/web/divkit/src/components/gallery/Gallery.module.css":"divkit/public/client/web/divkit/src/components/gallery/Gallery.module.css",
|
||||
"client/web/divkit/src/components/gallery/Gallery.svelte":"divkit/public/client/web/divkit/src/components/gallery/Gallery.svelte",
|
||||
"client/web/divkit/src/components/grid/Grid.module.css":"divkit/public/client/web/divkit/src/components/grid/Grid.module.css",
|
||||
@@ -11969,6 +11989,7 @@
|
||||
"client/web/divkit/src/context/state.ts":"divkit/public/client/web/divkit/src/context/state.ts",
|
||||
"client/web/divkit/src/dev.html":"divkit/public/client/web/divkit/src/dev.html",
|
||||
"client/web/divkit/src/dev.ts":"divkit/public/client/web/divkit/src/dev.ts",
|
||||
"client/web/divkit/src/devCustomComponents.ts":"divkit/public/client/web/divkit/src/devCustomComponents.ts",
|
||||
"client/web/divkit/src/expressions/ast.d.ts":"divkit/public/client/web/divkit/src/expressions/ast.d.ts",
|
||||
"client/web/divkit/src/expressions/bigint.ts":"divkit/public/client/web/divkit/src/expressions/bigint.ts",
|
||||
"client/web/divkit/src/expressions/const.ts":"divkit/public/client/web/divkit/src/expressions/const.ts",
|
||||
@@ -12003,6 +12024,7 @@
|
||||
"client/web/divkit/src/types/base.d.ts":"divkit/public/client/web/divkit/src/types/base.d.ts",
|
||||
"client/web/divkit/src/types/border.d.ts":"divkit/public/client/web/divkit/src/types/border.d.ts",
|
||||
"client/web/divkit/src/types/container.d.ts":"divkit/public/client/web/divkit/src/types/container.d.ts",
|
||||
"client/web/divkit/src/types/custom.d.ts":"divkit/public/client/web/divkit/src/types/custom.d.ts",
|
||||
"client/web/divkit/src/types/drawable.d.ts":"divkit/public/client/web/divkit/src/types/drawable.d.ts",
|
||||
"client/web/divkit/src/types/edgeInserts.d.ts":"divkit/public/client/web/divkit/src/types/edgeInserts.d.ts",
|
||||
"client/web/divkit/src/types/filter.d.ts":"divkit/public/client/web/divkit/src/types/filter.d.ts",
|
||||
@@ -14164,6 +14186,7 @@
|
||||
"client/web/divkit/typings/client-hydratable.d.ts":"divkit/public/client/web/divkit/typings/client-hydratable.d.ts",
|
||||
"client/web/divkit/typings/client.d.ts":"divkit/public/client/web/divkit/typings/client.d.ts",
|
||||
"client/web/divkit/typings/common.d.ts":"divkit/public/client/web/divkit/typings/common.d.ts",
|
||||
"client/web/divkit/typings/custom.d.ts":"divkit/public/client/web/divkit/typings/custom.d.ts",
|
||||
"client/web/divkit/typings/extensions.d.ts":"divkit/public/client/web/divkit/typings/extensions.d.ts",
|
||||
"client/web/divkit/typings/server.d.ts":"divkit/public/client/web/divkit/typings/server.d.ts",
|
||||
"client/web/divkit/typings/variables.d.ts":"divkit/public/client/web/divkit/typings/variables.d.ts",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
save-exact=true
|
||||
@@ -0,0 +1,42 @@
|
||||
## Using DivKit components inside your Custom Component
|
||||
|
||||
[General notes about custom components](../../divkit/README.md#customComponents)
|
||||
|
||||
[Previous example](../custom-simple)
|
||||
|
||||
Install the packages first:
|
||||
|
||||
```
|
||||
npm ci
|
||||
```
|
||||
|
||||
And then open `index.html` file with any http server to see the result (the browser may block loading of external modules into the file:// pages).
|
||||
|
||||
### How can support a DivKitComponents inside my Custom Component?
|
||||
|
||||
First of all, lets look at the markup:
|
||||
|
||||
```js
|
||||
// DivJson
|
||||
"type": "custom",
|
||||
"custom_type": "custom_container",
|
||||
"items": [
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
These `items` are the DivKit markup and will be rendered by DivKit itself.
|
||||
|
||||
Now we will provide a place (`slot`) for these `items`:
|
||||
|
||||
```js
|
||||
// JS code
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
shadow.innerHTML = '<slot></slot>';
|
||||
```
|
||||
|
||||
This `<slot>` can be located at any depth of your element, not just a direct child of ShadowRoot.
|
||||
|
||||
### How can I render Custom DivKit components on the Server Side?
|
||||
|
||||
See [next example](../custom-ssr).
|
||||
@@ -0,0 +1,9 @@
|
||||
class CustomContainer extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
shadow.innerHTML = '<slot></slot>';
|
||||
}
|
||||
}
|
||||
customElements.define('custom-container', CustomContainer);
|
||||
@@ -0,0 +1,86 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" type="text/css" href="node_modules/@divkitframework/divkit/dist/client.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="module">
|
||||
import { render } from './node_modules/@divkitframework/divkit/dist/esm/client.mjs';
|
||||
import './customElement.js';
|
||||
|
||||
render({
|
||||
id: 'test',
|
||||
target: document.querySelector('#root'),
|
||||
customComponents: new Map([
|
||||
['custom_container', {
|
||||
element: 'custom-container'
|
||||
}]
|
||||
]),
|
||||
json: {
|
||||
"templates": {
|
||||
"news_item_template": {
|
||||
"type": "text",
|
||||
"font_size": 16,
|
||||
"line_height": 20,
|
||||
"text_color": "#333",
|
||||
"$text": "news_item_text",
|
||||
"paddings": {
|
||||
"left": 12,
|
||||
"right": 12,
|
||||
"top": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"log_id": "custom_card",
|
||||
"states": [
|
||||
{
|
||||
"state_id": 0,
|
||||
"div": {
|
||||
"type": "container",
|
||||
"margins": {
|
||||
"left": 16,
|
||||
"top": 16,
|
||||
"right": 16,
|
||||
"bottom": 16
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"type": "custom",
|
||||
"custom_type": "custom_container",
|
||||
"items": [
|
||||
{
|
||||
"type": "news_item_template",
|
||||
"news_item_text": "This is div-text item 1"
|
||||
},
|
||||
{
|
||||
"type": "news_item_template",
|
||||
"news_item_text": "This is div-text item 2"
|
||||
},
|
||||
{
|
||||
"type": "news_item_template",
|
||||
"news_item_text": "This is div-text item 3"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "custom-simple",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "custom-simple",
|
||||
"version": "1.0.0",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@divkitframework/divkit": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb"
|
||||
}
|
||||
},
|
||||
"node_modules/@divkitframework/divkit": {
|
||||
"version": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb",
|
||||
"resolved": "https://registry.npmjs.org/@divkitframework/divkit/-/divkit-28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb.tgz",
|
||||
"integrity": "sha512-EEqRHNF77NYZhVsYGxQLEku9q5tYniAjFJ0UzsxM2TMwt6lLmJ96hOZbMro6wM7Fh6XuwC3Kjkc6PQdPTUuIXw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "custom-simple",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@divkitframework/divkit": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
save-exact=true
|
||||
@@ -0,0 +1,94 @@
|
||||
## Using custom components
|
||||
|
||||
[General notes about custom components](../../divkit/README.md#customComponents)
|
||||
|
||||
Install the packages first:
|
||||
|
||||
```
|
||||
npm ci
|
||||
```
|
||||
|
||||
And then open `index.html` file with any http server to see the result (the browser may block loading of external modules into the file:// pages).
|
||||
|
||||
### What's the idea?
|
||||
|
||||
In json markup you can provide a special component:
|
||||
|
||||
```json
|
||||
// DivJson
|
||||
{
|
||||
"type": "custom",
|
||||
"custom_type": "custom_card"
|
||||
}
|
||||
```
|
||||
|
||||
Without any help, DivKit will not know about this special component. This can be done using the `customComponents` map:
|
||||
|
||||
```js
|
||||
// JS code
|
||||
render({
|
||||
id: 'test',
|
||||
target: document.querySelector('#root'),
|
||||
customComponents: new Map([
|
||||
['custom_card', {
|
||||
element: 'custom-card'
|
||||
}]
|
||||
]),
|
||||
json: {
|
||||
...
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Now we need to create our `custom-card` component. This can be done using the power of Custom Elements:
|
||||
|
||||
```js
|
||||
// JS Code
|
||||
class CustomCard extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// some logic
|
||||
}
|
||||
}
|
||||
customElements.define('custom-card', CustomCard);
|
||||
```
|
||||
|
||||
This code defines a custom element inside the browser. So, any `custom` component inside DivKit should be a Custom Element in terms of HTML.
|
||||
|
||||
### How can I provide additional data for a component?
|
||||
|
||||
`custom` components in DivKit can accept an additional field `custom_props`. This is an object, and all its properties are directly mapped as attributes of your Custom Element:
|
||||
|
||||
```js
|
||||
// DivJson
|
||||
"custom_props": {
|
||||
"start": 15
|
||||
}
|
||||
|
||||
// ->
|
||||
|
||||
// HTML
|
||||
<custom-card start="15"></custom-card>
|
||||
```
|
||||
|
||||
Keep in mind that there are no integer attributes here, all additional properties will be rendered as regular html attributes.
|
||||
|
||||
Next, your Custom Element should read these properties:
|
||||
|
||||
```js
|
||||
// JS code
|
||||
connectedCallback() {
|
||||
const start = this.getAttribute('start');
|
||||
}
|
||||
```
|
||||
|
||||
Keep in mind that these additional attributes will only be available in the `connectedCallback`, and not in the `constructor` of the Custom Element.
|
||||
|
||||
### What about browser support?
|
||||
|
||||
Custom elements have been supported in all modern browsers for a while. There are some nuances that will be covered in the following examples, but basic support for Custom Elements v1 will be a good start for your components.
|
||||
|
||||
### How can I wrap other DivKit components with my Custom Component?
|
||||
|
||||
See [next example](../custom-container).
|
||||
@@ -0,0 +1,56 @@
|
||||
class CustomCard extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
|
||||
const style = document.createElement('style');
|
||||
|
||||
style.textContent = `
|
||||
.component {
|
||||
padding: 30px;
|
||||
font-size: 20px;
|
||||
background: linear-gradient(85deg, #fff, #068200, #1eea00, #FF0000, #fcfa1e, #ccd49b, #7af719);
|
||||
}
|
||||
`;
|
||||
|
||||
const div = this._text = document.createElement('div');
|
||||
div.className = 'component';
|
||||
div.textContent = '00:00';
|
||||
|
||||
shadow.appendChild(style);
|
||||
shadow.appendChild(div);
|
||||
}
|
||||
|
||||
_updateText() {
|
||||
const minutes = Math.floor(this._counter / 60);
|
||||
const seconds = this._counter % 60;
|
||||
this._text.textContent = String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0');
|
||||
}
|
||||
|
||||
_tick() {
|
||||
this._timeout = window.setTimeout(() => {
|
||||
++this._counter;
|
||||
|
||||
this._updateText();
|
||||
|
||||
this._tick();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
const start = this.getAttribute('start');
|
||||
this._counter = Number(start) || 0;
|
||||
this._timeout = 0;
|
||||
this._updateText();
|
||||
this._tick();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('custom-card', CustomCard);
|
||||
@@ -0,0 +1,52 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" type="text/css" href="node_modules/@divkitframework/divkit/dist/client.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="module">
|
||||
import { render } from './node_modules/@divkitframework/divkit/dist/esm/client.mjs';
|
||||
import './customElement.js';
|
||||
|
||||
render({
|
||||
id: 'test',
|
||||
target: document.querySelector('#root'),
|
||||
customComponents: new Map([
|
||||
['custom_card', {
|
||||
element: 'custom-card'
|
||||
}]
|
||||
]),
|
||||
json: {
|
||||
"templates": {
|
||||
},
|
||||
"card": {
|
||||
"log_id": "snapshot_test_card",
|
||||
"states": [
|
||||
{
|
||||
"state_id": 0,
|
||||
"div": {
|
||||
"type": "custom",
|
||||
"custom_type": "custom_card",
|
||||
"custom_props": {
|
||||
"start": 15
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "custom-simple",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "custom-simple",
|
||||
"version": "1.0.0",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@divkitframework/divkit": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb"
|
||||
}
|
||||
},
|
||||
"node_modules/@divkitframework/divkit": {
|
||||
"version": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb",
|
||||
"resolved": "https://registry.npmjs.org/@divkitframework/divkit/-/divkit-28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb.tgz",
|
||||
"integrity": "sha512-EEqRHNF77NYZhVsYGxQLEku9q5tYniAjFJ0UzsxM2TMwt6lLmJ96hOZbMro6wM7Fh6XuwC3Kjkc6PQdPTUuIXw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "custom-simple",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@divkitframework/divkit": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
save-exact=true
|
||||
@@ -0,0 +1,131 @@
|
||||
## Using DivKit components inside your Custom Component
|
||||
|
||||
[General notes about custom components](../../divkit/README.md#customComponents)
|
||||
|
||||
[Previous example with a simple component](../custom-simple)
|
||||
|
||||
[Previous example with container component](../custom-container)
|
||||
|
||||
[General info about SSR in DivKit](../ssr)
|
||||
|
||||
This example assumes that you are familiar with the ideas from the previous examples.
|
||||
|
||||
Install the packages first:
|
||||
|
||||
```
|
||||
npm ci
|
||||
```
|
||||
|
||||
### Run sample
|
||||
|
||||
```shell
|
||||
npm start
|
||||
```
|
||||
|
||||
### How does it work without items?
|
||||
|
||||
The server code contains a similar `customComponents` map and will produce something like this:
|
||||
|
||||
```html
|
||||
// HTML
|
||||
<custom-container></custom-container>
|
||||
```
|
||||
|
||||
On the client side, this markup will be hydrated. So, what about markup inside the Custom Element?
|
||||
|
||||
Lets assume that our component has some markup, for example, `Hello custom container`:
|
||||
|
||||
```html
|
||||
// HTML
|
||||
<custom-container>
|
||||
<!-- rendered on client -->
|
||||
Hello custom container
|
||||
<!-- rendered on client -->
|
||||
</custom-container>
|
||||
```
|
||||
|
||||
Custom elements is defined on the client side, and their markup is created in the browser. This means that if we don't do anything, our glorious SSR (Server-side rendering) will produce incomplete markup, which will be changed after the Custom Element is defined. The component will work as expected, but the user may notice a layout shift and a markup change.
|
||||
|
||||
Declarative shadow dom comes to the rescue! If our `customComponents` contain not only the tag name of the Custom Element, but also a template itself, DivKit will produce a `<template>` for this component for us:
|
||||
|
||||
```js
|
||||
// JS code
|
||||
{
|
||||
element: 'custom-container',
|
||||
template: 'Hello custom container'
|
||||
}
|
||||
|
||||
// HTML
|
||||
<custom-container>
|
||||
<template shadowrootmode="open">
|
||||
Hello custom container
|
||||
</template>
|
||||
</custom-container>
|
||||
```
|
||||
|
||||
This markup will be parsed in the browser directly into the shadow dom:
|
||||
|
||||
```html
|
||||
<custom-container>
|
||||
#shadow-root (open)
|
||||
Hello custom container
|
||||
</custom-container>
|
||||
```
|
||||
|
||||
In this case, the browser will be able to show the exact page even without running JS on the client side, which means that there will be no layout shift on our page and it will be persisted during the entire page load.
|
||||
|
||||
Don't forget to change your custom element code:
|
||||
|
||||
```js
|
||||
// JS Code
|
||||
if (this.shadowRoot) {
|
||||
// Declarative shadow dom is supported and parsed by the browser
|
||||
} else {
|
||||
// Create a shadow root as usual
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### How it works with items?
|
||||
|
||||
Literally the same. Don't forget to include `<slot></slot>` in your template, and everything will be fine:
|
||||
|
||||
```js
|
||||
// JS code
|
||||
{
|
||||
element: 'custom-container',
|
||||
template: 'Hello custom container<slot></slot>'
|
||||
}
|
||||
|
||||
// HTML Server response
|
||||
<custom-container>
|
||||
<template shadowrootmode="open">
|
||||
Hello custom container
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<!--some DivKit markup-->
|
||||
</div>
|
||||
</custom-container>
|
||||
|
||||
// In the browser parsed HTML
|
||||
<custom-container>
|
||||
#shadow-root (open)
|
||||
Hello custom container
|
||||
<slot>
|
||||
↳
|
||||
<div>
|
||||
<!--some DivKit markup-->
|
||||
</div>
|
||||
</slot>
|
||||
</custom-container>
|
||||
```
|
||||
|
||||
### What about browser support?
|
||||
|
||||
Declarative Shadow DOM is currently supported only in Chromium-based browsers and in Safari. Firefox currently does not support this, but work on supporting this [will begin soon](https://github.com/mozilla/standards-positions/issues/335#issuecomment-1714182997).
|
||||
|
||||
### What about old browsers that will receive markup using that feature?
|
||||
|
||||
This will create a `<template>` element, but this template will not create shadow root automatically, so your component should do it the classic way. In conclusion, your Custom Component will work, but SSR will be "incomplete" in this browser, and the layout will be changed after JS execution.
|
||||
@@ -0,0 +1,11 @@
|
||||
class CustomContainer extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (!this.shadowRoot) {
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
shadow.innerHTML = 'Hello custom container<slot></slot>';
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('custom-container', CustomContainer);
|
||||
+666
@@ -0,0 +1,666 @@
|
||||
{
|
||||
"name": "custom-ssr",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "custom-ssr",
|
||||
"version": "1.0.0",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@divkitframework/divkit": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb",
|
||||
"express": "4.18.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@divkitframework/divkit": {
|
||||
"version": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb",
|
||||
"resolved": "https://registry.npmjs.org/@divkitframework/divkit/-/divkit-28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb.tgz",
|
||||
"integrity": "sha512-EEqRHNF77NYZhVsYGxQLEku9q5tYniAjFJ0UzsxM2TMwt6lLmJ96hOZbMro6wM7Fh6XuwC3Kjkc6PQdPTUuIXw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.11.0",
|
||||
"raw-body": "2.5.1",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.18.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
||||
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.1",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.5.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.2.0",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.11.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.18.0",
|
||||
"serve-static": "1.15.0",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "2.0.1",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
||||
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
|
||||
"integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
|
||||
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "2.4.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "custom-ssr",
|
||||
"version": "1.0.0",
|
||||
"private": "true",
|
||||
"keywords": [],
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@divkitframework/divkit": "28.6.0-canary-f1eee8005aa682b1f02f9f9caec744f889bc1fdb",
|
||||
"express": "4.18.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const express = require('express');
|
||||
const { render } = require('@divkitframework/divkit/server');
|
||||
|
||||
const PORT = 3123;
|
||||
|
||||
const template = fs.readFileSync(path.resolve(__dirname, 'template.html'), 'utf-8');
|
||||
|
||||
function jsonFilter(str) {
|
||||
return String(str).replace(/[<>&\u2028\u2029]/g, function(match) {
|
||||
switch (match) {
|
||||
case '<':
|
||||
return '\\u003c';
|
||||
case '>':
|
||||
return '\\u003e';
|
||||
case '&':
|
||||
return '\\u0026';
|
||||
case '\u2028':
|
||||
return '\\u2028';
|
||||
case '\u2029':
|
||||
return '\\u2029';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
const app = express();
|
||||
|
||||
const json = {
|
||||
"templates": {
|
||||
"news_item_template": {
|
||||
"type": "text",
|
||||
"font_size": 16,
|
||||
"line_height": 20,
|
||||
"text_color": "#333",
|
||||
"$text": "news_item_text",
|
||||
"paddings": {
|
||||
"left": 12,
|
||||
"right": 12,
|
||||
"top": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"log_id": "custom_card",
|
||||
"states": [
|
||||
{
|
||||
"state_id": 0,
|
||||
"div": {
|
||||
"type": "container",
|
||||
"margins": {
|
||||
"left": 16,
|
||||
"top": 16,
|
||||
"right": 16,
|
||||
"bottom": 16
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"type": "custom",
|
||||
"custom_type": "custom_container",
|
||||
"items": [
|
||||
{
|
||||
"type": "news_item_template",
|
||||
"news_item_text": "This is div-text item 1"
|
||||
},
|
||||
{
|
||||
"type": "news_item_template",
|
||||
"news_item_text": "This is div-text item 2"
|
||||
},
|
||||
{
|
||||
"type": "news_item_template",
|
||||
"news_item_text": "This is div-text item 3"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
app.get('/', (req, res, _next) => {
|
||||
const html = render({
|
||||
id: 'test',
|
||||
json,
|
||||
customComponents: new Map([
|
||||
['custom_container', {
|
||||
element: 'custom-container',
|
||||
template: 'Hello custom container<slot></slot>'
|
||||
}]
|
||||
])
|
||||
});
|
||||
|
||||
res.send(
|
||||
template
|
||||
.replace('[% html %]', () => html)
|
||||
.replace('[% json %]', () => jsonFilter(JSON.stringify(json)))
|
||||
);
|
||||
});
|
||||
|
||||
app.use(express.static(__dirname));
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server started on http://localhost:${PORT}/`);
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" type="text/css" href="node_modules/@divkitframework/divkit/dist/client.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="root">[% html %]</div>
|
||||
|
||||
<script type="text/json" id="json">
|
||||
[% json %]
|
||||
</script>
|
||||
<script type="module">
|
||||
import { render } from './node_modules/@divkitframework/divkit/dist/esm/client-hydratable.mjs';
|
||||
import './customElement.js';
|
||||
|
||||
const json = JSON.parse(document.querySelector('#json').textContent);
|
||||
render({
|
||||
id: 'test',
|
||||
target: document.querySelector('#root'),
|
||||
hydrate: true,
|
||||
json,
|
||||
customComponents: new Map([
|
||||
['custom_container', {
|
||||
element: 'custom-container'
|
||||
}]
|
||||
])
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -335,6 +335,22 @@ Also be aware of new releases (even minor and patch versions). Built-in extensio
|
||||
|
||||
[Building of a new extension](../divkit-examples/extensions-handmade/)
|
||||
|
||||
### customComponents
|
||||
|
||||
`Map<string, CustomComponentDescription>`, optional.
|
||||
|
||||
Custom components can be provided using the `customComponents` map. This structure maps names from json markup to html custom elements (or web components).
|
||||
|
||||
These components can provide any additional features that your application needs, such as map widgets, integration into social networks, YouTube videos, and so on.
|
||||
|
||||
Custom components is supported not only on the client side, but also on the server side. See the examples below for more detailed information:
|
||||
|
||||
[Simple custom component](../divkit-examples/custom-simple/)
|
||||
|
||||
[Custom component with the DivKit items](../divkit-examples/custom-container/)
|
||||
|
||||
[Custom component and SSR](../divkit-examples/custom-ssr/)
|
||||
|
||||
#### theme (EXPERIMENTAL)
|
||||
|
||||
`system` | `light` | `dark`
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
TypefaceProvider
|
||||
} from '../typings/common';
|
||||
import type { GlobalVariablesController } from './expressions/globalVariablesController';
|
||||
import type { CustomComponentDescription } from '../typings/custom';
|
||||
|
||||
export function render(opts: {
|
||||
target: HTMLElement;
|
||||
@@ -33,28 +34,14 @@ export function render(opts: {
|
||||
theme?: Theme;
|
||||
fetchInit?: FetchInit;
|
||||
tooltipRoot?: HTMLElement;
|
||||
customComponents?: Map<string, CustomComponentDescription> | undefined;
|
||||
}) {
|
||||
const { target, hydrate, ...rest } = opts;
|
||||
|
||||
return new Root({
|
||||
target: opts.target,
|
||||
props: {
|
||||
id: opts.id,
|
||||
json: opts.json,
|
||||
globalVariablesController: opts.globalVariablesController,
|
||||
mix: opts.mix,
|
||||
customization: opts.customization,
|
||||
builtinProtocols: opts.builtinProtocols,
|
||||
extensions: opts.extensions,
|
||||
onStat: opts.onStat,
|
||||
onCustomAction: opts.onCustomAction,
|
||||
onError: opts.onError,
|
||||
onComponent: opts.onComponent,
|
||||
typefaceProvider: opts.typefaceProvider,
|
||||
platform: opts.platform,
|
||||
theme: opts.theme,
|
||||
fetchInit: opts.fetchInit,
|
||||
tooltipRoot: opts.tooltipRoot
|
||||
},
|
||||
hydrate: opts.hydrate
|
||||
target: target,
|
||||
props: rest,
|
||||
hydrate: hydrate
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -13,5 +13,6 @@ module.exports.COMPONENTS = [
|
||||
'slider',
|
||||
'input',
|
||||
'select',
|
||||
'video'
|
||||
'video',
|
||||
'custom'
|
||||
];
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
DisappearAction,
|
||||
FetchInit
|
||||
} from '../../typings/common';
|
||||
import type { CustomComponentDescription } from '../../typings/custom';
|
||||
import type { AppearanceTransition, DivBaseData, Tooltip, TransitionChange } from '../types/base';
|
||||
import type { SwitchElements, Overflow } from '../types/switch-elements';
|
||||
import type { TintMode } from '../types/image';
|
||||
@@ -70,7 +71,6 @@
|
||||
} from '../expressions/globalVariablesController';
|
||||
import { getUrlSchema, isBuiltinSchema } from '../utils/url';
|
||||
import { TimersController } from '../utils/timers';
|
||||
import TooltipView from './tooltip/Tooltip.svelte';
|
||||
|
||||
export let id: string;
|
||||
export let json: Partial<DivJson> = {};
|
||||
@@ -88,6 +88,7 @@
|
||||
export let typefaceProvider: TypefaceProvider = _fontFamily => '';
|
||||
export let fetchInit: FetchInit = {};
|
||||
export let tooltipRoot: HTMLElement | undefined = undefined;
|
||||
export let customComponents: Map<string, CustomComponentDescription> | undefined = undefined;
|
||||
|
||||
let isDesktop = writable(platform === 'desktop');
|
||||
if (platform === 'auto' && typeof matchMedia !== 'undefined') {
|
||||
@@ -972,6 +973,7 @@
|
||||
typefaceProvider,
|
||||
isDesktop,
|
||||
isPointerFocus,
|
||||
customComponents,
|
||||
registerComponent: process.env.DEVTOOL ? registerComponentReal : undefined,
|
||||
unregisterComponent: process.env.DEVTOOL ? unregisterComponentReal : undefined
|
||||
});
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
|
||||
import type { LayoutParams } from '../../types/layoutParams';
|
||||
import type { DivBase, TemplateContext } from '../../../typings/common';
|
||||
import type { DivCustomData } from '../../types/custom';
|
||||
import type { DivBaseData } from '../../types/base';
|
||||
import type { CustomComponentDescription } from '../../../typings/custom';
|
||||
import Unknown from '../utilities/Unknown.svelte';
|
||||
import Outer from '../utilities/Outer.svelte';
|
||||
import { ROOT_CTX, RootCtxValue } from '../../context/root';
|
||||
import { wrapError } from '../../utils/wrapError';
|
||||
|
||||
export let json: Partial<DivCustomData> = {};
|
||||
export let templateContext: TemplateContext;
|
||||
export let origJson: DivBase | undefined = undefined;
|
||||
export let layoutParams: LayoutParams | undefined = undefined;
|
||||
|
||||
const rootCtx = getContext<RootCtxValue>(ROOT_CTX);
|
||||
|
||||
let desc: CustomComponentDescription | null = null;
|
||||
let templateContent = '';
|
||||
// shadowrootmode is an unknown attribute in TS :(
|
||||
let templateAttrs: any = {};
|
||||
$: if (typeof json.custom_type === 'string' && json.custom_type && rootCtx.customComponents?.has(json.custom_type)) {
|
||||
desc = rootCtx.customComponents.get(json.custom_type)!;
|
||||
templateContent = desc.template || '';
|
||||
templateAttrs = {
|
||||
shadowrootmode: desc.shadowRootMode || 'open'
|
||||
};
|
||||
} else {
|
||||
desc = null;
|
||||
templateContent = ';';
|
||||
rootCtx.logError(wrapError(new Error('Unknown or incorrect "custom_type" prop for div "custom"')));
|
||||
}
|
||||
|
||||
let hasItemsError = false;
|
||||
$: jsonItems = json.items;
|
||||
$: {
|
||||
if (jsonItems !== undefined && !Array.isArray(jsonItems)) {
|
||||
hasItemsError = true;
|
||||
rootCtx.logError(wrapError(new Error('Incorrect "items" prop for div "custom"')));
|
||||
} else {
|
||||
hasItemsError = false;
|
||||
}
|
||||
}
|
||||
|
||||
$: items = (!hasItemsError && jsonItems || []).map(item => {
|
||||
let childJson: DivBaseData = item as DivBaseData;
|
||||
let childContext: TemplateContext = templateContext;
|
||||
|
||||
({
|
||||
templateContext: childContext,
|
||||
json: childJson
|
||||
} = rootCtx.processTemplate(childJson, childContext));
|
||||
|
||||
return {
|
||||
json: childJson,
|
||||
templateContext: childContext,
|
||||
origJson: item
|
||||
};
|
||||
});
|
||||
|
||||
$: childLayoutParams = {
|
||||
fakeElement: layoutParams?.fakeElement
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if desc}
|
||||
<Outer
|
||||
{json}
|
||||
{origJson}
|
||||
{templateContext}
|
||||
{layoutParams}
|
||||
>
|
||||
<svelte:element this={desc.element} {...(json.custom_props || {})}>
|
||||
{#if templateContent}
|
||||
<template {...templateAttrs}>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html templateContent}
|
||||
</template>
|
||||
{/if}
|
||||
|
||||
{#if !hasItemsError && jsonItems}
|
||||
{#key jsonItems}
|
||||
{#each items as item}
|
||||
<Unknown
|
||||
layoutParams={childLayoutParams}
|
||||
div={item.json}
|
||||
templateContext={item.templateContext}
|
||||
origJson={item.origJson}
|
||||
/>
|
||||
{/each}
|
||||
{/key}
|
||||
{/if}
|
||||
</svelte:element>
|
||||
</Outer>
|
||||
{/if}
|
||||
@@ -13,6 +13,7 @@ import Slider from './slider/Slider.svelte';
|
||||
import Input from './input/Input.svelte';
|
||||
import Select from './select/Select.svelte';
|
||||
import Video from './video/Video.svelte';
|
||||
import Custom from './custom/Custom.svelte';
|
||||
|
||||
export const TYPE_MAP: Record<string, ComponentType> = {};
|
||||
|
||||
@@ -75,3 +76,7 @@ if (process.env.ENABLE_COMPONENT_SELECT || process.env.ENABLE_COMPONENT_SELECT =
|
||||
if (process.env.ENABLE_COMPONENT_VIDEO || process.env.ENABLE_COMPONENT_VIDEO === undefined) {
|
||||
TYPE_MAP.video = Video;
|
||||
}
|
||||
|
||||
if (process.env.ENABLE_COMPONENT_CUSTOM || process.env.ENABLE_COMPONENT_CUSTOM === undefined) {
|
||||
TYPE_MAP.custom = Custom;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { MaybeMissing } from '../expressions/json';
|
||||
import type { Variable, VariableType } from '../expressions/variable';
|
||||
import type { TintMode } from '../types/image';
|
||||
import type { Customization } from '../../typings/common';
|
||||
import type { CustomComponentDescription } from '../../typings/custom';
|
||||
|
||||
export const ROOT_CTX = Symbol('root');
|
||||
|
||||
@@ -51,6 +52,7 @@ export interface RootCtxValue {
|
||||
isPointerFocus: Readable<boolean>;
|
||||
typefaceProvider: TypefaceProvider;
|
||||
isDesktop: Readable<boolean>;
|
||||
customComponents: Map<string, CustomComponentDescription> | undefined;
|
||||
|
||||
// Devtool
|
||||
registerComponent?({
|
||||
|
||||
@@ -3,6 +3,7 @@ import { SizeProvider } from './extensions/sizeProvider';
|
||||
import { lottieExtensionBuilder } from './extensions/lottie';
|
||||
import type { DivExtensionClass } from '../typings/common';
|
||||
import Lottie from 'lottie-web/build/player/lottie';
|
||||
import './devCustomComponents';
|
||||
|
||||
const json = {
|
||||
"templates": {},
|
||||
@@ -32,6 +33,23 @@ window.root = new Root({
|
||||
extensions: new Map<string, DivExtensionClass>([
|
||||
['size_provider', SizeProvider],
|
||||
['lottie', lottieExtensionBuilder(Lottie.loadAnimation)],
|
||||
]),
|
||||
customComponents: new Map([
|
||||
['old_custom_card_1', {
|
||||
element: 'old-custom-card1'
|
||||
}],
|
||||
['old_custom_card_2', {
|
||||
element: 'old-custom-card2'
|
||||
}],
|
||||
['new_custom_card_1', {
|
||||
element: 'new-custom-card'
|
||||
}],
|
||||
['new_custom_card_2', {
|
||||
element: 'new-custom-card'
|
||||
}],
|
||||
['new_custom_container_1', {
|
||||
element: 'new-custom-container'
|
||||
}]
|
||||
])
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
class OldCustomCard1 extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
shadow.textContent = 'hi, i\'m old card';
|
||||
}
|
||||
}
|
||||
class OldCustomCard2 extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
shadow.textContent = 'and i\'m old as well';
|
||||
}
|
||||
}
|
||||
class NewCustomCard extends HTMLElement {
|
||||
private _counter: number;
|
||||
private _timeout: number;
|
||||
private _text: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
|
||||
const style = document.createElement('style');
|
||||
|
||||
style.textContent = `
|
||||
.component {
|
||||
padding: 30px;
|
||||
font-size: 20px;
|
||||
background: linear-gradient(85deg, #000400, #068200, #1eea00, #FF0000, #fcfa1e, #ccd49b, #7af719);
|
||||
}
|
||||
`;
|
||||
|
||||
const div = this._text = document.createElement('div');
|
||||
div.className = 'component';
|
||||
div.textContent = '00:00';
|
||||
|
||||
shadow.appendChild(style);
|
||||
shadow.appendChild(div);
|
||||
|
||||
this._counter = 0;
|
||||
this._timeout = 0;
|
||||
this._tick();
|
||||
}
|
||||
|
||||
_tick() {
|
||||
this._timeout = window.setTimeout(() => {
|
||||
++this._counter;
|
||||
|
||||
const minutes = Math.floor(this._counter / 60);
|
||||
const seconds = this._counter % 60;
|
||||
this._text.textContent = String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0');
|
||||
|
||||
this._tick();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
class NewCustomContainer extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
shadow.innerHTML = '<slot></slot>';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('old-custom-card1', OldCustomCard1);
|
||||
customElements.define('old-custom-card2', OldCustomCard2);
|
||||
customElements.define('new-custom-card', NewCustomCard);
|
||||
customElements.define('new-custom-container', NewCustomContainer);
|
||||
@@ -1,6 +1,7 @@
|
||||
import Root from './components/Root.svelte';
|
||||
import type { Platform, TypefaceProvider } from '../typings/common';
|
||||
import type { GlobalVariablesController } from './expressions/globalVariablesController';
|
||||
import type { CustomComponentDescription } from '../typings/custom';
|
||||
|
||||
export function render(opts: {
|
||||
json: unknown;
|
||||
@@ -12,19 +13,10 @@ export function render(opts: {
|
||||
onError?: unknown;
|
||||
typefaceProvider?: TypefaceProvider;
|
||||
platform?: Platform;
|
||||
customComponents?: Map<string, CustomComponentDescription> | undefined;
|
||||
}) {
|
||||
// Root has client-side typings, not a server one
|
||||
return (Root as any).render({
|
||||
id: opts.id,
|
||||
json: opts.json,
|
||||
globalVariablesController: opts.globalVariablesController,
|
||||
mix: opts.mix,
|
||||
customization: opts.customization,
|
||||
builtinProtocols: opts.builtinProtocols,
|
||||
onError: opts.onError,
|
||||
typefaceProvider: opts.typefaceProvider,
|
||||
platform: opts.platform
|
||||
}).html;
|
||||
return (Root as any).render(opts).html;
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import type { DivBaseData } from './base';
|
||||
|
||||
export interface DivCustomData extends DivBaseData {
|
||||
type: 'custom';
|
||||
|
||||
items?: DivBaseData[];
|
||||
custom_type: string;
|
||||
custom_props?: object;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
} from './common';
|
||||
import type { GlobalVariablesController, Variable } from './variables';
|
||||
import type { WrappedError } from './common';
|
||||
import type { CustomComponentDescription } from './custom';
|
||||
|
||||
export interface DivkitDebugInstance extends DivkitInstance {
|
||||
getDebugVariables(): Map<string, Variable>;
|
||||
@@ -39,6 +40,7 @@ export function render(opts: {
|
||||
theme?: Theme;
|
||||
fetchInit?: FetchInit;
|
||||
tooltipRoot?: HTMLElement;
|
||||
customComponents?: Map<string, CustomComponentDescription> | undefined;
|
||||
}): DivkitDebugInstance;
|
||||
|
||||
export { createVariable, createGlobalVariablesController } from './variables';
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
TypefaceProvider,
|
||||
FetchInit
|
||||
} from './common';
|
||||
import type { CustomComponentDescription } from './custom';
|
||||
import type { GlobalVariablesController } from './variables';
|
||||
|
||||
export function render(opts: {
|
||||
@@ -32,6 +33,7 @@ export function render(opts: {
|
||||
theme?: Theme;
|
||||
fetchInit?: FetchInit;
|
||||
tooltipRoot?: HTMLElement;
|
||||
customComponents?: Map<string, CustomComponentDescription> | undefined;
|
||||
}): DivkitInstance;
|
||||
|
||||
export { createVariable, createGlobalVariablesController } from './variables';
|
||||
|
||||
+2
@@ -11,6 +11,7 @@ import type {
|
||||
TypefaceProvider,
|
||||
FetchInit
|
||||
} from './common';
|
||||
import type { CustomComponentDescription } from './custom';
|
||||
import type { GlobalVariablesController } from './variables';
|
||||
|
||||
export function render(opts: {
|
||||
@@ -31,6 +32,7 @@ export function render(opts: {
|
||||
theme?: Theme;
|
||||
fetchInit?: FetchInit;
|
||||
tooltipRoot?: HTMLElement;
|
||||
customComponents?: Map<string, CustomComponentDescription> | undefined;
|
||||
}): DivkitInstance;
|
||||
|
||||
export { createVariable, createGlobalVariablesController } from './variables';
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export interface CustomComponentDescription {
|
||||
element: string;
|
||||
shadowRootMode?: 'open' | 'close';
|
||||
template?: string;
|
||||
}
|
||||
+2
@@ -5,6 +5,7 @@ import type {
|
||||
Customization,
|
||||
TypefaceProvider
|
||||
} from './common';
|
||||
import type { CustomComponentDescription } from './custom';
|
||||
|
||||
export function render(opts: {
|
||||
json: DivJson;
|
||||
@@ -15,4 +16,5 @@ export function render(opts: {
|
||||
builtinProtocols?: string[];
|
||||
onError?: ErrorCallback;
|
||||
typefaceProvider?: TypefaceProvider;
|
||||
customComponents?: Map<string, CustomComponentDescription> | undefined;
|
||||
}): string;
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
{
|
||||
"java_extends": "DivBlockWithId",
|
||||
"$description": "translations.json#/div_custom",
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "div-base.json"
|
||||
|
||||
+1
-5
@@ -44,11 +44,7 @@
|
||||
},
|
||||
{
|
||||
"$ref": "div-custom.json",
|
||||
"$description": "translations.json#/div_custom_short",
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios"
|
||||
]
|
||||
"$description": "translations.json#/div_custom_short"
|
||||
},
|
||||
{
|
||||
"$ref": "div-indicator.json",
|
||||
|
||||
@@ -1564,18 +1564,10 @@
|
||||
},
|
||||
{
|
||||
"title": "Custom container",
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios"
|
||||
],
|
||||
"file": "custom-container.json"
|
||||
},
|
||||
{
|
||||
"title": "Custom card",
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios"
|
||||
],
|
||||
"file": "custom-card.json"
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user