diff --git a/.gitignore b/.gitignore
index ada2d4bd1..d8ccc7731 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,8 @@ node_modules
playwright-report*
/blob-report/
.cache*
+
+# Playground
+/playground/node_modules
+/playground/.next
+/playground/.env
diff --git a/.prettierignore b/.prettierignore
index a84998f2d..2d6035968 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -15,7 +15,11 @@ CONTRIBUTING.md
/widget
/schema
+
/playwright/playwright/.cache/
# npm files
-package.json
\ No newline at end of file
+package.json
+
+# Playground
+/playground/.next
diff --git a/README.md b/README.md
index 1f9485904..bf619e326 100644
--- a/README.md
+++ b/README.md
@@ -526,7 +526,7 @@ If you want to release a new version in previous major after commit it to the ma
7. Check your changes in CHANGELOG.md and approve robot's PR.
8. Squash and merge PR. You can see release process on [the Actions tab](https://github.com/gravity-ui/page-constructor/actions).
-## Page constructor editor
+## Page Constructor Editor v1
Editor provides user interface for page content management with realtime preview.
@@ -546,6 +546,36 @@ export const MyAppEditor = ({initialContent, onChange, transformContent}: MyAppE
);
```
+## Page Constructor Editor v2
+
+Editor provides user interface for page content management with realtime preview.
+
+Based on Iframe postMessage communication.
+
+How to use:
+
+```tsx
+import {Editor} from '@gravity-ui/page-constructor/editor-v2';
+
+export const MyAppEditor = ({initialContent, onUpdate, disableUrlField}: MyAppEditorProps) => (
+
+);
+```
+
+### How to develop
+
+```shell
+npm run deps:install
+npm run dev:playground
+```
+
+Directory `/playground` contains NextJS service with integrated PC and Editor for development purposes.
+
## Tests
Comprehensive documentation is available at the provided [link](./test-utils/docs/README.md).
diff --git a/package-lock.json b/package-lock.json
index 3906af152..f7624c0ec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@gravity-ui/page-constructor",
- "version": "6.8.1",
+ "version": "6.3.2-alpha.12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@gravity-ui/page-constructor",
- "version": "6.8.1",
+ "version": "6.3.2-alpha.12",
"license": "MIT",
"dependencies": {
"@bem-react/classname": "^1.6.0",
@@ -16,14 +16,17 @@
"@react-spring/web": "^9.7.3",
"ajv": "^8.12.0",
"ajv-keywords": "^5.1.0",
+ "deep-object-diff": "^1.1.9",
"final-form": "^4.20.9",
"github-buttons": "2.23.0",
+ "immutable": "^4.3.7",
"js-yaml-source-map": "^0.2.2",
"lodash": "^4.17.21",
"monaco-editor": "^0.38.0",
"react-final-form": "^6.5.9",
"react-monaco-editor": "^0.53.0",
"react-player": "^2.9.0",
+ "react-resizable-panels": "^2.1.3",
"react-slick": "^0.29.0",
"react-transition-group": "^4.4.2",
"react-waypoint": "^10.1.0",
@@ -32,7 +35,8 @@
"swiper": "^6.8.4",
"typograf": "^7.4.1",
"utility-types": "^3.10.0",
- "uuid": "^9.0.0"
+ "uuid": "^9.0.0",
+ "zustand": "^4.5.2"
},
"devDependencies": {
"@babel/core": "^7.22.8",
@@ -77,10 +81,13 @@
"@types/uuid": "^9.0.0",
"@types/webpack-env": "^1.18.1",
"@types/youtube-player": "^5.5.11",
+ "@vitejs/plugin-react": "^4.5.0",
"autoprefixer": "^10.4.14",
"babel-loader": "^8.3.0",
+ "bem-cn-lite": "^4.1.0",
"css-loader": "^5.2.7",
"es5-ext": "0.10.53",
+ "esbuild-sass-plugin": "^3.3.1",
"eslint": "^8.57.1",
"eslint-plugin-no-not-accumulator-reassign": "^0.1.0",
"eslint-plugin-react": "^7.37.4",
@@ -111,6 +118,7 @@
"react": "^18.3.1",
"react-docgen-typescript": "^2.2.2",
"react-dom": "^18.3.1",
+ "react-router": "^7.6.1",
"resolve-url-loader": "^3.1.5",
"rimraf": "^6.0.1",
"sass": "^1.63.6",
@@ -123,8 +131,9 @@
"ts-jest": "^29.2.5",
"tslib": "^2.4.0",
"typescript": "^5.7.3",
+ "vite": "^6.3.5",
"vite-plugin-commonjs": "^0.10.1",
- "vite-plugin-svgr": "^4.2.0",
+ "vite-plugin-svgr": "^4.3.0",
"webpack": "^5.98.0",
"webpack-cli": "^6.0.1",
"webpack-shell-plugin-next": "^2.3.1"
@@ -155,44 +164,47 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
- "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.27.1",
"js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
+ "picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/compat-data": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz",
- "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz",
+ "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz",
- "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==",
+ "version": "7.27.4",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz",
+ "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.5",
- "@babel/helper-compilation-targets": "^7.26.5",
- "@babel/helper-module-transforms": "^7.26.0",
- "@babel/helpers": "^7.26.7",
- "@babel/parser": "^7.26.7",
- "@babel/template": "^7.25.9",
- "@babel/traverse": "^7.26.7",
- "@babel/types": "^7.26.7",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.3",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.27.3",
+ "@babel/helpers": "^7.27.4",
+ "@babel/parser": "^7.27.4",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.27.4",
+ "@babel/types": "^7.27.3",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -250,13 +262,14 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz",
- "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz",
+ "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/parser": "^7.26.5",
- "@babel/types": "^7.26.5",
+ "@babel/parser": "^7.27.3",
+ "@babel/types": "^7.27.3",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^3.0.2"
@@ -278,13 +291,14 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz",
- "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==",
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.26.5",
- "@babel/helper-validator-option": "^7.25.9",
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
@@ -403,27 +417,29 @@
}
},
"node_modules/@babel/helper-module-imports": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
- "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.25.9",
- "@babel/types": "^7.25.9"
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
- "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
+ "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/helper-module-imports": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9",
- "@babel/traverse": "^7.25.9"
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.3"
},
"engines": {
"node": ">=6.9.0"
@@ -445,10 +461,11 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
- "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.9.0"
}
@@ -501,28 +518,31 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
- "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
- "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
- "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.9.0"
}
@@ -542,25 +562,27 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz",
- "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==",
+ "version": "7.27.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz",
+ "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/template": "^7.25.9",
- "@babel/types": "^7.26.7"
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.27.3"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
- "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
+ "version": "7.27.4",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz",
+ "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/types": "^7.26.7"
+ "@babel/types": "^7.27.3"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -1534,12 +1556,13 @@
}
},
"node_modules/@babel/plugin-transform-react-jsx-self": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz",
- "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.22.5"
+ "@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -1549,12 +1572,13 @@
}
},
"node_modules/@babel/plugin-transform-react-jsx-source": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz",
- "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.22.5"
+ "@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -1930,41 +1954,43 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
- "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
+ "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"dependencies": {
- "regenerator-runtime": "^0.13.11"
+ "regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
- "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.25.9",
- "@babel/parser": "^7.25.9",
- "@babel/types": "^7.25.9"
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz",
- "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==",
+ "version": "7.27.4",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz",
+ "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.5",
- "@babel/parser": "^7.26.7",
- "@babel/template": "^7.25.9",
- "@babel/types": "^7.26.7",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.3",
+ "@babel/parser": "^7.27.4",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.27.3",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -1973,13 +1999,14 @@
}
},
"node_modules/@babel/types": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
- "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz",
+ "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -3424,6 +3451,7 @@
"ppc64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"aix"
@@ -4969,368 +4997,1187 @@
"node": ">=12.4.0"
}
},
- "node_modules/@pkgr/core": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
- "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
- "dev": true,
- "engines": {
- "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/unts"
- }
- },
- "node_modules/@playwright/experimental-ct-core": {
- "version": "1.45.3",
- "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.45.3.tgz",
- "integrity": "sha512-uYcWBxRPu2G2Mj2e+XUxRBzRNnG/Yz0A5DVWFewiG3qEfC92MaGYGxmzKeFeU9NcMA2fWwaqB3XWHXjn9qSM5Q==",
+ "node_modules/@parcel/watcher": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
+ "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
"dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
"dependencies": {
- "playwright": "1.45.3",
- "playwright-core": "1.45.3",
- "vite": "^5.2.8"
+ "detect-libc": "^1.0.3",
+ "is-glob": "^4.0.3",
+ "micromatch": "^4.0.5",
+ "node-addon-api": "^7.0.0"
},
"engines": {
- "node": ">=18"
- }
- },
- "node_modules/@playwright/experimental-ct-react": {
- "version": "1.45.3",
- "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.45.3.tgz",
- "integrity": "sha512-cEgiZ2+DqVCeFJWJdHgOUwhlEof41Rg1GX48FtMX/xrjAnxs2ccqqAXMILD+mVV1ftsC2jeS1EiRLoAmf8QXgA==",
- "dev": true,
- "dependencies": {
- "@playwright/experimental-ct-core": "1.45.3",
- "@vitejs/plugin-react": "^4.2.1"
+ "node": ">= 10.0.0"
},
- "bin": {
- "playwright": "cli.js"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
},
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@playwright/test": {
- "version": "1.45.3",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.3.tgz",
- "integrity": "sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==",
+ "optionalDependencies": {
+ "@parcel/watcher-android-arm64": "2.5.1",
+ "@parcel/watcher-darwin-arm64": "2.5.1",
+ "@parcel/watcher-darwin-x64": "2.5.1",
+ "@parcel/watcher-freebsd-x64": "2.5.1",
+ "@parcel/watcher-linux-arm-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm-musl": "2.5.1",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm64-musl": "2.5.1",
+ "@parcel/watcher-linux-x64-glibc": "2.5.1",
+ "@parcel/watcher-linux-x64-musl": "2.5.1",
+ "@parcel/watcher-win32-arm64": "2.5.1",
+ "@parcel/watcher-win32-ia32": "2.5.1",
+ "@parcel/watcher-win32-x64": "2.5.1"
+ }
+ },
+ "node_modules/@parcel/watcher-android-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
+ "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "dependencies": {
- "playwright": "1.45.3"
- },
- "bin": {
- "playwright": "cli.js"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": ">=18"
- }
- },
- "node_modules/@react-spring/animated": {
- "version": "9.7.3",
- "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz",
- "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==",
- "dependencies": {
- "@react-spring/shared": "~9.7.3",
- "@react-spring/types": "~9.7.3"
- },
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
- }
- },
- "node_modules/@react-spring/core": {
- "version": "9.7.3",
- "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz",
- "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==",
- "dependencies": {
- "@react-spring/animated": "~9.7.3",
- "@react-spring/shared": "~9.7.3",
- "@react-spring/types": "~9.7.3"
+ "node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
- "url": "https://opencollective.com/react-spring/donate"
- },
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ "url": "https://opencollective.com/parcel"
}
},
- "node_modules/@react-spring/shared": {
- "version": "9.7.3",
- "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz",
- "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==",
- "dependencies": {
- "@react-spring/types": "~9.7.3"
+ "node_modules/@parcel/watcher-darwin-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
+ "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
},
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
}
},
- "node_modules/@react-spring/types": {
- "version": "9.7.3",
- "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz",
- "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw=="
- },
- "node_modules/@react-spring/web": {
- "version": "9.7.3",
- "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz",
- "integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==",
- "dependencies": {
- "@react-spring/animated": "~9.7.3",
- "@react-spring/core": "~9.7.3",
- "@react-spring/shared": "~9.7.3",
- "@react-spring/types": "~9.7.3"
+ "node_modules/@parcel/watcher-darwin-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
+ "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
},
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
- "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
}
},
- "node_modules/@rollup/pluginutils": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
- "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
+ "node_modules/@parcel/watcher-freebsd-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
+ "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "dependencies": {
- "@types/estree": "^1.0.0",
- "estree-walker": "^2.0.2",
- "picomatch": "^2.3.1"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ "node": ">= 10.0.0"
},
- "peerDependenciesMeta": {
- "rollup": {
- "optional": true
- }
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
}
},
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.38.0.tgz",
- "integrity": "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==",
+ "node_modules/@parcel/watcher-linux-arm-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
+ "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "android"
- ]
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
},
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.38.0.tgz",
- "integrity": "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==",
+ "node_modules/@parcel/watcher-linux-arm-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
+ "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
"cpu": [
- "arm64"
+ "arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "android"
- ]
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
},
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.38.0.tgz",
- "integrity": "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==",
+ "node_modules/@parcel/watcher-linux-arm64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
+ "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.38.0.tgz",
- "integrity": "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==",
- "cpu": [
- "x64"
+ "linux"
],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ]
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
},
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.38.0.tgz",
- "integrity": "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==",
+ "node_modules/@parcel/watcher-linux-arm64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
+ "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "freebsd"
- ]
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
},
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.38.0.tgz",
- "integrity": "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==",
+ "node_modules/@parcel/watcher-linux-x64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
+ "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "freebsd"
- ]
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
},
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.38.0.tgz",
- "integrity": "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==",
+ "node_modules/@parcel/watcher-linux-x64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
+ "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
"cpu": [
- "arm"
+ "x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
},
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.38.0.tgz",
- "integrity": "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==",
+ "node_modules/@parcel/watcher-win32-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
+ "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
"cpu": [
- "arm"
+ "arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
},
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.38.0.tgz",
- "integrity": "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==",
+ "node_modules/@parcel/watcher-win32-ia32": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
+ "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
"cpu": [
- "arm64"
+ "ia32"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
},
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.38.0.tgz",
- "integrity": "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==",
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
+ "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
"cpu": [
- "arm64"
+ "x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
},
- "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.38.0.tgz",
- "integrity": "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==",
+ "node_modules/@pkgr/core": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
+ "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core": {
+ "version": "1.45.3",
+ "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.45.3.tgz",
+ "integrity": "sha512-uYcWBxRPu2G2Mj2e+XUxRBzRNnG/Yz0A5DVWFewiG3qEfC92MaGYGxmzKeFeU9NcMA2fWwaqB3XWHXjn9qSM5Q==",
+ "dev": true,
+ "dependencies": {
+ "playwright": "1.45.3",
+ "playwright-core": "1.45.3",
+ "vite": "^5.2.8"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
"cpu": [
- "loong64"
+ "arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
},
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.38.0.tgz",
- "integrity": "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==",
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
"cpu": [
- "ppc64"
+ "arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
},
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.38.0.tgz",
- "integrity": "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==",
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
"cpu": [
- "riscv64"
+ "x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
},
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz",
- "integrity": "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==",
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
"cpu": [
- "riscv64"
+ "arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
},
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.38.0.tgz",
- "integrity": "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==",
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
"cpu": [
- "s390x"
+ "x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
},
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.38.0.tgz",
- "integrity": "sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==",
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
"cpu": [
- "x64"
+ "arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
},
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.38.0.tgz",
- "integrity": "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==",
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.38.0",
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/@playwright/experimental-ct-core/node_modules/vite": {
+ "version": "5.4.19",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
+ "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@playwright/experimental-ct-react": {
+ "version": "1.45.3",
+ "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.45.3.tgz",
+ "integrity": "sha512-cEgiZ2+DqVCeFJWJdHgOUwhlEof41Rg1GX48FtMX/xrjAnxs2ccqqAXMILD+mVV1ftsC2jeS1EiRLoAmf8QXgA==",
+ "dev": true,
+ "dependencies": {
+ "@playwright/experimental-ct-core": "1.45.3",
+ "@vitejs/plugin-react": "^4.2.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@playwright/test": {
+ "version": "1.45.3",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.3.tgz",
+ "integrity": "sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==",
+ "dev": true,
+ "dependencies": {
+ "playwright": "1.45.3"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@react-spring/animated": {
+ "version": "9.7.3",
+ "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz",
+ "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==",
+ "dependencies": {
+ "@react-spring/shared": "~9.7.3",
+ "@react-spring/types": "~9.7.3"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spring/core": {
+ "version": "9.7.3",
+ "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz",
+ "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==",
+ "dependencies": {
+ "@react-spring/animated": "~9.7.3",
+ "@react-spring/shared": "~9.7.3",
+ "@react-spring/types": "~9.7.3"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-spring/donate"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spring/shared": {
+ "version": "9.7.3",
+ "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz",
+ "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==",
+ "dependencies": {
+ "@react-spring/types": "~9.7.3"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spring/types": {
+ "version": "9.7.3",
+ "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz",
+ "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw=="
+ },
+ "node_modules/@react-spring/web": {
+ "version": "9.7.3",
+ "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz",
+ "integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==",
+ "dependencies": {
+ "@react-spring/animated": "~9.7.3",
+ "@react-spring/core": "~9.7.3",
+ "@react-spring/shared": "~9.7.3",
+ "@react-spring/types": "~9.7.3"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.9",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz",
+ "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
+ "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.38.0.tgz",
+ "integrity": "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.38.0.tgz",
+ "integrity": "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.38.0.tgz",
+ "integrity": "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.38.0.tgz",
+ "integrity": "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.38.0.tgz",
+ "integrity": "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.38.0.tgz",
+ "integrity": "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.38.0.tgz",
+ "integrity": "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.38.0.tgz",
+ "integrity": "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.38.0.tgz",
+ "integrity": "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.38.0.tgz",
+ "integrity": "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.38.0.tgz",
+ "integrity": "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.38.0.tgz",
+ "integrity": "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.38.0.tgz",
+ "integrity": "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz",
+ "integrity": "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.38.0.tgz",
+ "integrity": "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.38.0.tgz",
+ "integrity": "sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.38.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.38.0.tgz",
+ "integrity": "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.38.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.38.0.tgz",
"integrity": "sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==",
"cpu": [
@@ -7564,22 +8411,24 @@
"dev": true
},
"node_modules/@vitejs/plugin-react": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz",
- "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==",
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz",
+ "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/core": "^7.23.5",
- "@babel/plugin-transform-react-jsx-self": "^7.23.3",
- "@babel/plugin-transform-react-jsx-source": "^7.23.3",
+ "@babel/core": "^7.26.10",
+ "@babel/plugin-transform-react-jsx-self": "^7.25.9",
+ "@babel/plugin-transform-react-jsx-source": "^7.25.9",
+ "@rolldown/pluginutils": "1.0.0-beta.9",
"@types/babel__core": "^7.20.5",
- "react-refresh": "^0.14.0"
+ "react-refresh": "^0.17.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0"
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
}
},
"node_modules/@webassemblyjs/ast": {
@@ -8675,6 +9524,23 @@
}
]
},
+ "node_modules/bem-cn": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/bem-cn/-/bem-cn-3.0.1.tgz",
+ "integrity": "sha512-kWC76a09vSk6cJXDYsH1erjxdBf856HTxl0IHOvYItSmBC6wQCsRCf9bmKR0hmeUDcUP5XPMr8MNXDgKbKJi0A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bem-cn-lite": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bem-cn-lite/-/bem-cn-lite-4.1.0.tgz",
+ "integrity": "sha512-0IEVRYK2MQKQO00P3sY3hNv7vH8P+Z8mR46qFcaiwsQAWp0MuMWpLSuUUhZEjKD2HzTGXMqMsFysWEeeJa1drQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bem-cn": "^3.0.1"
+ }
+ },
"node_modules/better-opn": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz",
@@ -9607,6 +10473,16 @@
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"dev": true
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/copy-to-clipboard": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
@@ -10305,6 +11181,11 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
+ "node_modules/deep-object-diff": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz",
+ "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA=="
+ },
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -10383,6 +11264,20 @@
"node": ">=0.10.0"
}
},
+ "node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -11082,6 +11977,22 @@
"esbuild": ">=0.12 <1"
}
},
+ "node_modules/esbuild-sass-plugin": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-3.3.1.tgz",
+ "integrity": "sha512-SnO1ls+d52n6j8gRRpjexXI8MsHEaumS0IdDHaYM29Y6gakzZYMls6i9ql9+AWMSQk/eryndmUpXEgT34QrX1A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve": "^1.22.8",
+ "safe-identifier": "^0.4.2",
+ "sass": "^1.71.1"
+ },
+ "peerDependencies": {
+ "esbuild": ">=0.20.1",
+ "sass-embedded": "^1.71.1"
+ }
+ },
"node_modules/esbuild/node_modules/@esbuild/aix-ppc64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
@@ -11876,7 +12787,8 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/esutils": {
"version": "2.0.3",
@@ -12096,6 +13008,21 @@
"bser": "2.1.1"
}
},
+ "node_modules/fdir": {
+ "version": "6.4.5",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz",
+ "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -14284,8 +15211,7 @@
"node_modules/immutable": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
- "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
- "dev": true
+ "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw=="
},
"node_modules/import-fresh": {
"version": "3.3.0",
@@ -18032,6 +18958,14 @@
"integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==",
"dev": true
},
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -20529,14 +21463,47 @@
"dev": true
},
"node_modules/react-refresh": {
- "version": "0.14.0",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
- "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==",
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
+ "node_modules/react-resizable-panels": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz",
+ "integrity": "sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==",
+ "peerDependencies": {
+ "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.6.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.1.tgz",
+ "integrity": "sha512-hPJXXxHJZEsPFNVbtATH7+MMX43UDeOauz+EAU4cgqTn7ojdI9qQORqS8Z0qmDlL1TclO/6jLRYUEtbWidtdHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-select": {
"version": "5.7.4",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.4.tgz",
@@ -20875,9 +21842,9 @@
}
},
"node_modules/regenerator-runtime": {
- "version": "0.13.11",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
- "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/regenerator-transform": {
"version": "0.15.2",
@@ -21740,6 +22707,13 @@
}
]
},
+ "node_modules/safe-identifier": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz",
+ "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/safe-push-apply": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
@@ -21857,13 +22831,14 @@
}
},
"node_modules/sass": {
- "version": "1.63.6",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz",
- "integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==",
+ "version": "1.89.1",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.1.tgz",
+ "integrity": "sha512-eMLLkl+qz7tx/0cJ9wI+w09GQ2zodTkcE/aVfywwdlRcI3EO19xGnbmJwg/JMIm+5MxVJ6outddLZ4Von4E++Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "chokidar": ">=3.0.0 <4.0.0",
- "immutable": "^4.0.0",
+ "chokidar": "^4.0.0",
+ "immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
@@ -21871,6 +22846,9 @@
},
"engines": {
"node": ">=14.0.0"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher": "^2.4.1"
}
},
"node_modules/sass-loader": {
@@ -21946,17 +22924,54 @@
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
- "dependencies": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
- },
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/sass/node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/sass/node_modules/immutable": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz",
+ "integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/sass/node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
"engines": {
- "node": ">= 10.13.0"
+ "node": ">= 14.18.0"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
}
},
"node_modules/saxes": {
@@ -22056,6 +23071,13 @@
"randombytes": "^2.1.0"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -22320,558 +23342,235 @@
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
- "dev": true,
- "dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/source-map-support/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-url": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
- "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
- "deprecated": "See https://github.com/lydell/source-map-url#deprecated",
- "dev": true
- },
- "node_modules/spdx-correct": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
- "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
- "dev": true,
- "dependencies": {
- "spdx-expression-parse": "^3.0.0",
- "spdx-license-ids": "^3.0.0"
- }
- },
- "node_modules/spdx-exceptions": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
- "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
- "dev": true
- },
- "node_modules/spdx-expression-parse": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
- "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
- "dev": true,
- "dependencies": {
- "spdx-exceptions": "^2.1.0",
- "spdx-license-ids": "^3.0.0"
- }
- },
- "node_modules/spdx-license-ids": {
- "version": "3.0.12",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz",
- "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==",
- "dev": true
- },
- "node_modules/split2": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
- "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
- "dev": true,
- "engines": {
- "node": ">= 10.x"
- }
- },
- "node_modules/sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
- "dev": true
- },
- "node_modules/ssr-window": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-3.0.0.tgz",
- "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA=="
- },
- "node_modules/stable-hash": {
- "version": "0.0.4",
- "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz",
- "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==",
- "dev": true
- },
- "node_modules/stack-utils": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
- "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
- "dev": true,
- "dependencies": {
- "escape-string-regexp": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/stack-utils/node_modules/escape-string-regexp": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
- "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/stop-iteration-iterator": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
- "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==",
- "dev": true,
- "dependencies": {
- "internal-slot": "^1.0.4"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/storybook": {
- "version": "8.6.11",
- "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.11.tgz",
- "integrity": "sha512-B2wxpmq1QYS4JV7RQu1mOHD7akfoGbuoUSkx2D2GZgv/zXAHZmDpSFcTvvBBm8FAtzChI9HhITSJ0YS0ePfnJQ==",
- "dev": true,
- "dependencies": {
- "@storybook/core": "8.6.11"
- },
- "bin": {
- "getstorybook": "bin/index.cjs",
- "sb": "bin/index.cjs",
- "storybook": "bin/index.cjs"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/storybook"
- },
- "peerDependencies": {
- "prettier": "^2 || ^3"
- },
- "peerDependenciesMeta": {
- "prettier": {
- "optional": true
- }
- }
- },
- "node_modules/stream-composer": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz",
- "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==",
- "dev": true,
- "dependencies": {
- "streamx": "^2.13.2"
- }
- },
- "node_modules/stream-exhaust": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz",
- "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==",
- "dev": true
- },
- "node_modules/stream-shift": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
- "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
- "dev": true
- },
- "node_modules/streamx": {
- "version": "2.22.0",
- "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz",
- "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==",
- "dev": true,
- "dependencies": {
- "fast-fifo": "^1.3.2",
- "text-decoder": "^1.1.0"
- },
- "optionalDependencies": {
- "bare-events": "^2.2.0"
- }
- },
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "dev": true,
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
- "node_modules/string-argv": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz",
- "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==",
- "dev": true,
- "engines": {
- "node": ">=0.6.19"
- }
- },
- "node_modules/string-convert": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
- "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="
- },
- "node_modules/string-hash": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz",
- "integrity": "sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==",
- "dev": true
- },
- "node_modules/string-length": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
- "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
- "dev": true,
- "dependencies": {
- "char-regex": "^1.0.2",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/string-replace-loader": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-3.1.0.tgz",
- "integrity": "sha512-5AOMUZeX5HE/ylKDnEa/KKBqvlnFmRZudSOjVJHxhoJg9QYTwl1rECx7SLR8BBH7tfxb4Rp7EM2XVfQFxIhsbQ==",
- "dev": true,
- "dependencies": {
- "loader-utils": "^2.0.0",
- "schema-utils": "^3.0.0"
- },
- "peerDependencies": {
- "webpack": "^5"
- }
- },
- "node_modules/string-replace-loader/node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
+ "dev": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
}
},
- "node_modules/string-replace-loader/node_modules/ajv-keywords": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
- "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "node_modules/source-map-support/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
- "peerDependencies": {
- "ajv": "^6.9.1"
+ "engines": {
+ "node": ">=0.10.0"
}
},
- "node_modules/string-replace-loader/node_modules/json-schema-traverse": {
+ "node_modules/source-map-url": {
"version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
+ "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
+ "deprecated": "See https://github.com/lydell/source-map-url#deprecated",
"dev": true
},
- "node_modules/string-replace-loader/node_modules/schema-utils": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
- "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "node_modules/spdx-correct": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
+ "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
"dev": true,
"dependencies": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
}
},
- "node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "node_modules/spdx-exceptions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+ "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+ "dev": true
+ },
+ "node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
"dev": true,
"dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
}
},
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "node_modules/spdx-license-ids": {
+ "version": "3.0.12",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz",
+ "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==",
+ "dev": true
+ },
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"dev": true,
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
"engines": {
- "node": ">=8"
+ "node": ">= 10.x"
}
},
- "node_modules/string.prototype.includes": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz",
- "integrity": "sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==",
- "dev": true,
- "dependencies": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.17.5"
- }
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true
},
- "node_modules/string.prototype.matchall": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
- "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
+ "node_modules/ssr-window": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-3.0.0.tgz",
+ "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA=="
+ },
+ "node_modules/stable-hash": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz",
+ "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==",
+ "dev": true
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.6",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.6",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "internal-slot": "^1.1.0",
- "regexp.prototype.flags": "^1.5.3",
- "set-function-name": "^2.0.2",
- "side-channel": "^1.1.0"
+ "escape-string-regexp": "^2.0.0"
},
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=10"
}
},
- "node_modules/string.prototype.padend": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz",
- "integrity": "sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw==",
+ "node_modules/stack-utils/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
"dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- },
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=8"
}
},
- "node_modules/string.prototype.repeat": {
+ "node_modules/stop-iteration-iterator": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
- "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
- "dev": true,
- "dependencies": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.17.5"
- }
- },
- "node_modules/string.prototype.trim": {
- "version": "1.2.10",
- "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
- "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
+ "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "define-data-property": "^1.1.4",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-object-atoms": "^1.0.0",
- "has-property-descriptors": "^1.0.2"
+ "internal-slot": "^1.0.4"
},
"engines": {
"node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/string.prototype.trimend": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
- "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
+ "node_modules/storybook": {
+ "version": "8.6.11",
+ "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.11.tgz",
+ "integrity": "sha512-B2wxpmq1QYS4JV7RQu1mOHD7akfoGbuoUSkx2D2GZgv/zXAHZmDpSFcTvvBBm8FAtzChI9HhITSJ0YS0ePfnJQ==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
+ "@storybook/core": "8.6.11"
},
- "engines": {
- "node": ">= 0.4"
+ "bin": {
+ "getstorybook": "bin/index.cjs",
+ "sb": "bin/index.cjs",
+ "storybook": "bin/index.cjs"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/string.prototype.trimstart": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
- "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
},
- "engines": {
- "node": ">= 0.4"
+ "peerDependencies": {
+ "prettier": "^2 || ^3"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "peerDependenciesMeta": {
+ "prettier": {
+ "optional": true
+ }
}
},
- "node_modules/stringify-object": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
- "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
+ "node_modules/stream-composer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz",
+ "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==",
"dev": true,
"dependencies": {
- "get-own-enumerable-property-symbols": "^3.0.0",
- "is-obj": "^1.0.1",
- "is-regexp": "^1.0.0"
- },
- "engines": {
- "node": ">=4"
+ "streamx": "^2.13.2"
}
},
- "node_modules/stringify-object/node_modules/is-obj": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
- "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
+ "node_modules/stream-exhaust": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz",
+ "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==",
+ "dev": true
+ },
+ "node_modules/stream-shift": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
+ "dev": true
},
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "node_modules/streamx": {
+ "version": "2.22.0",
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz",
+ "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==",
"dev": true,
"dependencies": {
- "ansi-regex": "^5.0.1"
+ "fast-fifo": "^1.3.2",
+ "text-decoder": "^1.1.0"
},
- "engines": {
- "node": ">=8"
+ "optionalDependencies": {
+ "bare-events": "^2.2.0"
}
},
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dev": true,
"dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
+ "safe-buffer": "~5.2.0"
}
},
- "node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "node_modules/string-argv": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz",
+ "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==",
"dev": true,
"engines": {
- "node": ">=4"
+ "node": ">=0.6.19"
}
},
- "node_modules/strip-bom-string": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
- "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
+ "node_modules/string-convert": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
+ "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="
},
- "node_modules/strip-final-newline": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
- "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
+ "node_modules/string-hash": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz",
+ "integrity": "sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==",
+ "dev": true
},
- "node_modules/strip-indent": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
- "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
"dev": true,
"dependencies": {
- "min-indent": "^1.0.0"
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
},
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=10"
}
},
- "node_modules/style-inject": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz",
- "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==",
- "dev": true
- },
- "node_modules/style-loader": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz",
- "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==",
+ "node_modules/string-replace-loader": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-3.1.0.tgz",
+ "integrity": "sha512-5AOMUZeX5HE/ylKDnEa/KKBqvlnFmRZudSOjVJHxhoJg9QYTwl1rECx7SLR8BBH7tfxb4Rp7EM2XVfQFxIhsbQ==",
"dev": true,
"dependencies": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
"peerDependencies": {
- "webpack": "^4.0.0 || ^5.0.0"
+ "webpack": "^5"
}
},
- "node_modules/style-loader/node_modules/ajv": {
+ "node_modules/string-replace-loader/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
@@ -22887,7 +23586,7 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/style-loader/node_modules/ajv-keywords": {
+ "node_modules/string-replace-loader/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
@@ -22896,503 +23595,344 @@
"ajv": "^6.9.1"
}
},
- "node_modules/style-loader/node_modules/json-schema-traverse": {
+ "node_modules/string-replace-loader/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
- "node_modules/style-loader/node_modules/schema-utils": {
+ "node_modules/string-replace-loader/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/style-search": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz",
- "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==",
- "dev": true
- },
- "node_modules/stylelint": {
- "version": "15.11.0",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.11.0.tgz",
- "integrity": "sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw==",
- "dev": true,
- "dependencies": {
- "@csstools/css-parser-algorithms": "^2.3.1",
- "@csstools/css-tokenizer": "^2.2.0",
- "@csstools/media-query-list-parser": "^2.1.4",
- "@csstools/selector-specificity": "^3.0.0",
- "balanced-match": "^2.0.0",
- "colord": "^2.9.3",
- "cosmiconfig": "^8.2.0",
- "css-functions-list": "^3.2.1",
- "css-tree": "^2.3.1",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.1",
- "fastest-levenshtein": "^1.0.16",
- "file-entry-cache": "^7.0.0",
- "global-modules": "^2.0.0",
- "globby": "^11.1.0",
- "globjoin": "^0.1.4",
- "html-tags": "^3.3.1",
- "ignore": "^5.2.4",
- "import-lazy": "^4.0.0",
- "imurmurhash": "^0.1.4",
- "is-plain-object": "^5.0.0",
- "known-css-properties": "^0.29.0",
- "mathml-tag-names": "^2.1.3",
- "meow": "^10.1.5",
- "micromatch": "^4.0.5",
- "normalize-path": "^3.0.0",
- "picocolors": "^1.0.0",
- "postcss": "^8.4.28",
- "postcss-resolve-nested-selector": "^0.1.1",
- "postcss-safe-parser": "^6.0.0",
- "postcss-selector-parser": "^6.0.13",
- "postcss-value-parser": "^4.2.0",
- "resolve-from": "^5.0.0",
- "string-width": "^4.2.3",
- "strip-ansi": "^6.0.1",
- "style-search": "^0.1.0",
- "supports-hyperlinks": "^3.0.0",
- "svg-tags": "^1.0.0",
- "table": "^6.8.1",
- "write-file-atomic": "^5.0.1"
- },
- "bin": {
- "stylelint": "bin/stylelint.mjs"
- },
- "engines": {
- "node": "^14.13.1 || >=16.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/stylelint"
- }
- },
- "node_modules/stylelint-order": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.4.tgz",
- "integrity": "sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==",
- "dev": true,
- "dependencies": {
- "postcss": "^8.4.32",
- "postcss-sorting": "^8.0.2"
- },
- "peerDependencies": {
- "stylelint": "^14.0.0 || ^15.0.0 || ^16.0.1"
- }
- },
- "node_modules/stylelint-prettier": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/stylelint-prettier/-/stylelint-prettier-4.1.0.tgz",
- "integrity": "sha512-dd653q/d1IfvsSQshz1uAMe+XDm6hfM/7XiFH0htYY8Lse/s5ERTg7SURQehZPwVvm/rs7AsFhda9EQ2E9TS0g==",
- "dev": true,
- "dependencies": {
- "prettier-linter-helpers": "^1.0.0"
- },
- "engines": {
- "node": "^14.17.0 || >=16.0.0"
- },
- "peerDependencies": {
- "prettier": ">=3.0.0",
- "stylelint": ">=15.8.0"
- }
- },
- "node_modules/stylelint-scss": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.3.2.tgz",
- "integrity": "sha512-4LzLaayFhFyneJwLo0IUa8knuIvj+zF0vBFueQs4e3tEaAMIQX8q5th8ziKkgOavr6y/y9yoBe+RXN/edwLzsQ==",
- "dev": true,
- "dependencies": {
- "known-css-properties": "^0.29.0",
- "postcss-media-query-parser": "^0.2.3",
- "postcss-resolve-nested-selector": "^0.1.1",
- "postcss-selector-parser": "^6.0.13",
- "postcss-value-parser": "^4.2.0"
- },
- "peerDependencies": {
- "stylelint": "^14.5.1 || ^15.0.0"
- }
- },
- "node_modules/stylelint/node_modules/balanced-match": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz",
- "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==",
- "dev": true
- },
- "node_modules/stylelint/node_modules/decamelize": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz",
- "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==",
- "dev": true,
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
"engines": {
- "node": ">=10"
+ "node": ">= 10.13.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
}
},
- "node_modules/stylelint/node_modules/file-entry-cache": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-7.0.2.tgz",
- "integrity": "sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g==",
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
- "flat-cache": "^3.2.0"
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
},
"engines": {
- "node": ">=12.0.0"
+ "node": ">=8"
}
},
- "node_modules/stylelint/node_modules/global-modules": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
- "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
- "global-prefix": "^3.0.0"
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
},
"engines": {
- "node": ">=6"
+ "node": ">=8"
}
},
- "node_modules/stylelint/node_modules/global-prefix": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
- "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
+ "node_modules/string.prototype.includes": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz",
+ "integrity": "sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==",
"dev": true,
"dependencies": {
- "ini": "^1.3.5",
- "kind-of": "^6.0.2",
- "which": "^1.3.1"
- },
- "engines": {
- "node": ">=6"
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
}
},
- "node_modules/stylelint/node_modules/indent-string": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
- "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.12",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
+ "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
"dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.6",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "regexp.prototype.flags": "^1.5.3",
+ "set-function-name": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
"engines": {
- "node": ">=12"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/stylelint/node_modules/meow": {
- "version": "10.1.5",
- "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz",
- "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==",
+ "node_modules/string.prototype.padend": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz",
+ "integrity": "sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw==",
"dev": true,
"dependencies": {
- "@types/minimist": "^1.2.2",
- "camelcase-keys": "^7.0.0",
- "decamelize": "^5.0.0",
- "decamelize-keys": "^1.1.0",
- "hard-rejection": "^2.1.0",
- "minimist-options": "4.1.0",
- "normalize-package-data": "^3.0.2",
- "read-pkg-up": "^8.0.0",
- "redent": "^4.0.0",
- "trim-newlines": "^4.0.2",
- "type-fest": "^1.2.2",
- "yargs-parser": "^20.2.9"
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
},
"engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/stylelint/node_modules/redent": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz",
- "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==",
+ "node_modules/string.prototype.repeat": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
+ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
"dev": true,
"dependencies": {
- "indent-string": "^5.0.0",
- "strip-indent": "^4.0.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
}
},
- "node_modules/stylelint/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
+ "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
"dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-data-property": "^1.1.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-object-atoms": "^1.0.0",
+ "has-property-descriptors": "^1.0.2"
+ },
"engines": {
- "node": ">=14"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/stylelint/node_modules/strip-indent": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz",
- "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==",
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
+ "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
"dev": true,
"dependencies": {
- "min-indent": "^1.0.1"
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
},
"engines": {
- "node": ">=12"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/stylelint/node_modules/type-fest": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
- "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
"dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/stylelint/node_modules/which": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
- "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "node_modules/stringify-object": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
+ "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
"dev": true,
"dependencies": {
- "isexe": "^2.0.0"
+ "get-own-enumerable-property-symbols": "^3.0.0",
+ "is-obj": "^1.0.1",
+ "is-regexp": "^1.0.0"
},
- "bin": {
- "which": "bin/which"
+ "engines": {
+ "node": ">=4"
}
},
- "node_modules/stylelint/node_modules/write-file-atomic": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
- "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+ "node_modules/stringify-object/node_modules/is-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+ "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==",
"dev": true,
- "dependencies": {
- "imurmurhash": "^0.1.4",
- "signal-exit": "^4.0.1"
- },
"engines": {
- "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ "node": ">=0.10.0"
}
},
- "node_modules/stylis": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
- "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
- "dev": true
- },
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
+ "ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
- "node_modules/supports-hyperlinks": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz",
- "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==",
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0",
- "supports-color": "^7.0.0"
+ "ansi-regex": "^5.0.1"
},
"engines": {
- "node": ">=14.18"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1"
+ "node": ">=8"
}
},
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
"dev": true,
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "engines": {
+ "node": ">=4"
}
},
- "node_modules/sver": {
- "version": "1.8.4",
- "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz",
- "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==",
+ "node_modules/strip-bom-string": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
+ "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
"dev": true,
- "optionalDependencies": {
- "semver": "^6.3.0"
+ "engines": {
+ "node": ">=0.10.0"
}
},
- "node_modules/sver/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"dev": true,
- "optional": true,
- "bin": {
- "semver": "bin/semver.js"
+ "engines": {
+ "node": ">=6"
}
},
- "node_modules/svg-parser": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
- "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
- "dev": true
- },
- "node_modules/svg-tags": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
- "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==",
- "dev": true
- },
- "node_modules/swiper": {
- "version": "6.8.4",
- "resolved": "https://registry.npmjs.org/swiper/-/swiper-6.8.4.tgz",
- "integrity": "sha512-O+buF9Q+sMA0H7luMS8R59hCaJKlpo8PXhQ6ZYu6Rn2v9OsFd4d1jmrv14QvxtQpKAvL/ZiovEeANI/uDGet7g==",
- "funding": [
- {
- "type": "patreon",
- "url": "https://www.patreon.com/vladimirkharlampidi"
- },
- {
- "type": "open_collective",
- "url": "http://opencollective.com/swiper"
- }
- ],
- "hasInstallScript": true,
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
"dependencies": {
- "dom7": "^3.0.0",
- "ssr-window": "^3.0.0"
+ "min-indent": "^1.0.0"
},
"engines": {
- "node": ">= 4.7.0"
+ "node": ">=8"
}
},
- "node_modules/symbol-tree": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
- "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
- "dev": true
- },
- "node_modules/synckit": {
- "version": "0.9.2",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
- "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
- "dependencies": {
- "@pkgr/core": "^0.1.0",
- "tslib": "^2.6.2"
- },
"engines": {
- "node": "^14.18.0 || >=16.0.0"
+ "node": ">=8"
},
"funding": {
- "url": "https://opencollective.com/unts"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/tabbable": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
- "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
+ "node_modules/style-inject": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz",
+ "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==",
"dev": true
},
- "node_modules/table": {
- "version": "6.9.0",
- "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz",
- "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==",
+ "node_modules/style-loader": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz",
+ "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==",
"dev": true,
"dependencies": {
- "ajv": "^8.0.1",
- "lodash.truncate": "^4.4.2",
- "slice-ansi": "^4.0.0",
- "string-width": "^4.2.3",
- "strip-ansi": "^6.0.1"
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
},
"engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/tapable": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
- "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
- "dev": true,
- "engines": {
- "node": ">=6"
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.0.0 || ^5.0.0"
}
},
- "node_modules/teex": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
- "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
+ "node_modules/style-loader/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
- "streamx": "^2.12.5"
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/terser": {
- "version": "4.8.1",
- "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz",
- "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==",
+ "node_modules/style-loader/node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
- "dependencies": {
- "commander": "^2.20.0",
- "source-map": "~0.6.1",
- "source-map-support": "~0.5.12"
- },
- "bin": {
- "terser": "bin/terser"
- },
- "engines": {
- "node": ">=6.0.0"
+ "peerDependencies": {
+ "ajv": "^6.9.1"
}
},
- "node_modules/terser-webpack-plugin": {
- "version": "5.3.14",
- "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz",
- "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
+ "node_modules/style-loader/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/style-loader/node_modules/schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"dependencies": {
- "@jridgewell/trace-mapping": "^0.3.25",
- "jest-worker": "^27.4.5",
- "schema-utils": "^4.3.0",
- "serialize-javascript": "^6.0.2",
- "terser": "^5.31.1"
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
@@ -23400,1774 +23940,1921 @@
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/style-search": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz",
+ "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==",
+ "dev": true
+ },
+ "node_modules/stylelint": {
+ "version": "15.11.0",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.11.0.tgz",
+ "integrity": "sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw==",
+ "dev": true,
+ "dependencies": {
+ "@csstools/css-parser-algorithms": "^2.3.1",
+ "@csstools/css-tokenizer": "^2.2.0",
+ "@csstools/media-query-list-parser": "^2.1.4",
+ "@csstools/selector-specificity": "^3.0.0",
+ "balanced-match": "^2.0.0",
+ "colord": "^2.9.3",
+ "cosmiconfig": "^8.2.0",
+ "css-functions-list": "^3.2.1",
+ "css-tree": "^2.3.1",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.1",
+ "fastest-levenshtein": "^1.0.16",
+ "file-entry-cache": "^7.0.0",
+ "global-modules": "^2.0.0",
+ "globby": "^11.1.0",
+ "globjoin": "^0.1.4",
+ "html-tags": "^3.3.1",
+ "ignore": "^5.2.4",
+ "import-lazy": "^4.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-plain-object": "^5.0.0",
+ "known-css-properties": "^0.29.0",
+ "mathml-tag-names": "^2.1.3",
+ "meow": "^10.1.5",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.28",
+ "postcss-resolve-nested-selector": "^0.1.1",
+ "postcss-safe-parser": "^6.0.0",
+ "postcss-selector-parser": "^6.0.13",
+ "postcss-value-parser": "^4.2.0",
+ "resolve-from": "^5.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "style-search": "^0.1.0",
+ "supports-hyperlinks": "^3.0.0",
+ "svg-tags": "^1.0.0",
+ "table": "^6.8.1",
+ "write-file-atomic": "^5.0.1"
},
- "peerDependencies": {
- "webpack": "^5.1.0"
+ "bin": {
+ "stylelint": "bin/stylelint.mjs"
},
- "peerDependenciesMeta": {
- "@swc/core": {
- "optional": true
- },
- "esbuild": {
- "optional": true
- },
- "uglify-js": {
- "optional": true
- }
+ "engines": {
+ "node": "^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/stylelint"
}
},
- "node_modules/terser-webpack-plugin/node_modules/jest-worker": {
- "version": "27.5.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
- "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+ "node_modules/stylelint-order": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.4.tgz",
+ "integrity": "sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==",
"dev": true,
"dependencies": {
- "@types/node": "*",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.0.0"
+ "postcss": "^8.4.32",
+ "postcss-sorting": "^8.0.2"
},
- "engines": {
- "node": ">= 10.13.0"
+ "peerDependencies": {
+ "stylelint": "^14.0.0 || ^15.0.0 || ^16.0.1"
}
},
- "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
- "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
+ "node_modules/stylelint-prettier": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/stylelint-prettier/-/stylelint-prettier-4.1.0.tgz",
+ "integrity": "sha512-dd653q/d1IfvsSQshz1uAMe+XDm6hfM/7XiFH0htYY8Lse/s5ERTg7SURQehZPwVvm/rs7AsFhda9EQ2E9TS0g==",
"dev": true,
"dependencies": {
- "@types/json-schema": "^7.0.9",
- "ajv": "^8.9.0",
- "ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.1.0"
+ "prettier-linter-helpers": "^1.0.0"
},
"engines": {
- "node": ">= 10.13.0"
+ "node": "^14.17.0 || >=16.0.0"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
+ "peerDependencies": {
+ "prettier": ">=3.0.0",
+ "stylelint": ">=15.8.0"
}
},
- "node_modules/terser-webpack-plugin/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "node_modules/stylelint-scss": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.3.2.tgz",
+ "integrity": "sha512-4LzLaayFhFyneJwLo0IUa8knuIvj+zF0vBFueQs4e3tEaAMIQX8q5th8ziKkgOavr6y/y9yoBe+RXN/edwLzsQ==",
"dev": true,
"dependencies": {
- "has-flag": "^4.0.0"
+ "known-css-properties": "^0.29.0",
+ "postcss-media-query-parser": "^0.2.3",
+ "postcss-resolve-nested-selector": "^0.1.1",
+ "postcss-selector-parser": "^6.0.13",
+ "postcss-value-parser": "^4.2.0"
},
+ "peerDependencies": {
+ "stylelint": "^14.5.1 || ^15.0.0"
+ }
+ },
+ "node_modules/stylelint/node_modules/balanced-match": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz",
+ "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==",
+ "dev": true
+ },
+ "node_modules/stylelint/node_modules/decamelize": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz",
+ "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==",
+ "dev": true,
"engines": {
"node": ">=10"
},
"funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/terser-webpack-plugin/node_modules/terser": {
- "version": "5.39.0",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
- "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
+ "node_modules/stylelint/node_modules/file-entry-cache": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-7.0.2.tgz",
+ "integrity": "sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g==",
"dev": true,
"dependencies": {
- "@jridgewell/source-map": "^0.3.3",
- "acorn": "^8.8.2",
- "commander": "^2.20.0",
- "source-map-support": "~0.5.20"
- },
- "bin": {
- "terser": "bin/terser"
+ "flat-cache": "^3.2.0"
},
"engines": {
- "node": ">=10"
- }
- },
- "node_modules/terser/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
+ "node": ">=12.0.0"
}
},
- "node_modules/test-exclude": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "node_modules/stylelint/node_modules/global-modules": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
+ "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
"dev": true,
"dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
+ "global-prefix": "^3.0.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=6"
}
},
- "node_modules/text-decoder": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
- "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
+ "node_modules/stylelint/node_modules/global-prefix": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
+ "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
"dev": true,
"dependencies": {
- "b4a": "^1.6.4"
+ "ini": "^1.3.5",
+ "kind-of": "^6.0.2",
+ "which": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=6"
}
},
- "node_modules/text-extensions": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz",
- "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==",
+ "node_modules/stylelint/node_modules/indent-string": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
+ "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
"dev": true,
"engines": {
- "node": ">=8"
+ "node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/text-table": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
- "dev": true
- },
- "node_modules/textextensions": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz",
- "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==",
+ "node_modules/stylelint/node_modules/meow": {
+ "version": "10.1.5",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz",
+ "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==",
"dev": true,
+ "dependencies": {
+ "@types/minimist": "^1.2.2",
+ "camelcase-keys": "^7.0.0",
+ "decamelize": "^5.0.0",
+ "decamelize-keys": "^1.1.0",
+ "hard-rejection": "^2.1.0",
+ "minimist-options": "4.1.0",
+ "normalize-package-data": "^3.0.2",
+ "read-pkg-up": "^8.0.0",
+ "redent": "^4.0.0",
+ "trim-newlines": "^4.0.2",
+ "type-fest": "^1.2.2",
+ "yargs-parser": "^20.2.9"
+ },
"engines": {
- "node": ">=8"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
- "url": "https://bevry.me/fund"
- }
- },
- "node_modules/through": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
- "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
- "dev": true
- },
- "node_modules/through2": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
- "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==",
- "dev": true,
- "dependencies": {
- "readable-stream": "3"
- }
- },
- "node_modules/through2-filter": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz",
- "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==",
- "dev": true,
- "dependencies": {
- "through2": "~2.0.0",
- "xtend": "~4.0.0"
- }
- },
- "node_modules/through2-filter/node_modules/readable-stream": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
- "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
- "dev": true,
- "dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/through2-filter/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
- },
- "node_modules/through2-filter/node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "node_modules/stylelint/node_modules/redent": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz",
+ "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==",
"dev": true,
"dependencies": {
- "safe-buffer": "~5.1.0"
+ "indent-string": "^5.0.0",
+ "strip-indent": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/through2-filter/node_modules/through2": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
- "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "node_modules/stylelint/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
- "dependencies": {
- "readable-stream": "~2.3.6",
- "xtend": "~4.0.1"
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/timers-ext": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz",
- "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==",
+ "node_modules/stylelint/node_modules/strip-indent": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz",
+ "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==",
"dev": true,
"dependencies": {
- "es5-ext": "^0.10.64",
- "next-tick": "^1.1.0"
+ "min-indent": "^1.0.1"
},
"engines": {
- "node": ">=0.12"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/timers-ext/node_modules/es5-ext": {
- "version": "0.10.64",
- "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
- "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
+ "node_modules/stylelint/node_modules/type-fest": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
+ "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
"dev": true,
- "hasInstallScript": true,
- "dependencies": {
- "es6-iterator": "^2.0.3",
- "es6-symbol": "^3.1.3",
- "esniff": "^2.0.1",
- "next-tick": "^1.1.0"
- },
"engines": {
- "node": ">=0.10"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/timers-ext/node_modules/next-tick": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
- "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
- "dev": true
- },
- "node_modules/tiny-invariant": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
- "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
- "dev": true
- },
- "node_modules/tinyexec": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
- "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
- "dev": true
- },
- "node_modules/tmpl": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
- "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
- "dev": true
- },
- "node_modules/to-absolute-glob": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz",
- "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==",
+ "node_modules/stylelint/node_modules/which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"dependencies": {
- "is-absolute": "^1.0.0",
- "is-negated-glob": "^1.0.0"
+ "isexe": "^2.0.0"
},
- "engines": {
- "node": ">=0.10.0"
+ "bin": {
+ "which": "bin/which"
}
},
- "node_modules/to-regex-range": {
+ "node_modules/stylelint/node_modules/write-file-atomic": {
"version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
"dev": true,
"dependencies": {
- "is-number": "^7.0.0"
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
},
"engines": {
- "node": ">=8.0"
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
- "node_modules/to-through": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz",
- "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==",
+ "node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
+ "dev": true
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
- "through2": "^2.0.3"
+ "has-flag": "^4.0.0"
},
"engines": {
- "node": ">= 0.10"
+ "node": ">=8"
}
},
- "node_modules/to-through/node_modules/readable-stream": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
- "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "node_modules/supports-hyperlinks": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz",
+ "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==",
"dev": true,
"dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
+ "has-flag": "^4.0.0",
+ "supports-color": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1"
}
},
- "node_modules/to-through/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
},
- "node_modules/to-through/node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "node_modules/sver": {
+ "version": "1.8.4",
+ "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz",
+ "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==",
"dev": true,
- "dependencies": {
- "safe-buffer": "~5.1.0"
+ "optionalDependencies": {
+ "semver": "^6.3.0"
}
},
- "node_modules/to-through/node_modules/through2": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
- "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "node_modules/sver/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
- "dependencies": {
- "readable-stream": "~2.3.6",
- "xtend": "~4.0.1"
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
}
},
- "node_modules/toggle-selection": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
- "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
+ "node_modules/svg-parser": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
+ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
"dev": true
},
- "node_modules/tough-cookie": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
- "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
- "dev": true,
+ "node_modules/svg-tags": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
+ "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==",
+ "dev": true
+ },
+ "node_modules/swiper": {
+ "version": "6.8.4",
+ "resolved": "https://registry.npmjs.org/swiper/-/swiper-6.8.4.tgz",
+ "integrity": "sha512-O+buF9Q+sMA0H7luMS8R59hCaJKlpo8PXhQ6ZYu6Rn2v9OsFd4d1jmrv14QvxtQpKAvL/ZiovEeANI/uDGet7g==",
+ "funding": [
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/vladimirkharlampidi"
+ },
+ {
+ "type": "open_collective",
+ "url": "http://opencollective.com/swiper"
+ }
+ ],
+ "hasInstallScript": true,
"dependencies": {
- "psl": "^1.1.33",
- "punycode": "^2.1.1",
- "universalify": "^0.2.0",
- "url-parse": "^1.5.3"
+ "dom7": "^3.0.0",
+ "ssr-window": "^3.0.0"
},
"engines": {
- "node": ">=6"
+ "node": ">= 4.7.0"
}
},
- "node_modules/tough-cookie/node_modules/universalify": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
- "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
- "dev": true,
- "engines": {
- "node": ">= 4.0.0"
- }
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true
},
- "node_modules/trim-newlines": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz",
- "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==",
+ "node_modules/synckit": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
+ "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
"dev": true,
+ "dependencies": {
+ "@pkgr/core": "^0.1.0",
+ "tslib": "^2.6.2"
+ },
"engines": {
- "node": ">=12"
+ "node": "^14.18.0 || >=16.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://opencollective.com/unts"
}
},
- "node_modules/trough": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
- "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
+ "node_modules/tabbable": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
+ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
+ "dev": true
+ },
+ "node_modules/table": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz",
+ "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==",
"dev": true,
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
+ "dependencies": {
+ "ajv": "^8.0.1",
+ "lodash.truncate": "^4.4.2",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
}
},
- "node_modules/ts-api-utils": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
- "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+ "node_modules/tapable": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
"dev": true,
"engines": {
- "node": ">=16"
- },
- "peerDependencies": {
- "typescript": ">=4.2.0"
+ "node": ">=6"
}
},
- "node_modules/ts-dedent": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
- "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
+ "node_modules/teex": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
+ "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
"dev": true,
- "engines": {
- "node": ">=6.10"
+ "dependencies": {
+ "streamx": "^2.12.5"
}
},
- "node_modules/ts-jest": {
- "version": "29.2.5",
- "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz",
- "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==",
+ "node_modules/terser": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz",
+ "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==",
"dev": true,
"dependencies": {
- "bs-logger": "^0.2.6",
- "ejs": "^3.1.10",
- "fast-json-stable-stringify": "^2.1.0",
- "jest-util": "^29.0.0",
- "json5": "^2.2.3",
- "lodash.memoize": "^4.1.2",
- "make-error": "^1.3.6",
- "semver": "^7.6.3",
- "yargs-parser": "^21.1.1"
+ "commander": "^2.20.0",
+ "source-map": "~0.6.1",
+ "source-map-support": "~0.5.12"
},
"bin": {
- "ts-jest": "cli.js"
+ "terser": "bin/terser"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/terser-webpack-plugin": {
+ "version": "5.3.14",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz",
+ "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jest-worker": "^27.4.5",
+ "schema-utils": "^4.3.0",
+ "serialize-javascript": "^6.0.2",
+ "terser": "^5.31.1"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
},
"peerDependencies": {
- "@babel/core": ">=7.0.0-beta.0 <8",
- "@jest/transform": "^29.0.0",
- "@jest/types": "^29.0.0",
- "babel-jest": "^29.0.0",
- "jest": "^29.0.0",
- "typescript": ">=4.3 <6"
+ "webpack": "^5.1.0"
},
"peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
- },
- "@jest/transform": {
- "optional": true
- },
- "@jest/types": {
+ "@swc/core": {
"optional": true
},
- "babel-jest": {
+ "esbuild": {
"optional": true
},
- "esbuild": {
+ "uglify-js": {
"optional": true
}
}
},
- "node_modules/ts-jest/node_modules/yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "node_modules/terser-webpack-plugin/node_modules/jest-worker": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
+ "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
"engines": {
- "node": ">=12"
+ "node": ">= 10.13.0"
}
},
- "node_modules/tsconfig-paths": {
- "version": "3.15.0",
- "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
- "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
+ "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
+ "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
"dev": true,
"dependencies": {
- "@types/json5": "^0.0.29",
- "json5": "^1.0.2",
- "minimist": "^1.2.6",
- "strip-bom": "^3.0.0"
+ "@types/json-schema": "^7.0.9",
+ "ajv": "^8.9.0",
+ "ajv-formats": "^2.1.1",
+ "ajv-keywords": "^5.1.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
}
},
- "node_modules/tsconfig-paths/node_modules/json5": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
- "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "node_modules/terser-webpack-plugin/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"dependencies": {
- "minimist": "^1.2.0"
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/terser-webpack-plugin/node_modules/terser": {
+ "version": "5.39.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
+ "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/source-map": "^0.3.3",
+ "acorn": "^8.8.2",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
},
"bin": {
- "json5": "lib/cli.js"
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
}
},
- "node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ "node_modules/terser/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
},
- "node_modules/tsutils": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
- "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
"dev": true,
"dependencies": {
- "tslib": "^1.8.1"
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
},
"engines": {
- "node": ">= 6"
+ "node": ">=8"
+ }
+ },
+ "node_modules/text-decoder": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
+ "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
+ "dev": true,
+ "dependencies": {
+ "b4a": "^1.6.4"
+ }
+ },
+ "node_modules/text-extensions": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz",
+ "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
},
- "peerDependencies": {
- "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/tsutils/node_modules/tslib": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
- "node_modules/type": {
- "version": "2.7.3",
- "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
- "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==",
+ "node_modules/textextensions": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz",
+ "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://bevry.me/fund"
+ }
+ },
+ "node_modules/through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"dev": true
},
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "node_modules/through2": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
+ "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": "3"
+ }
+ },
+ "node_modules/through2-filter": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz",
+ "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==",
"dev": true,
"dependencies": {
- "prelude-ls": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.8.0"
+ "through2": "~2.0.0",
+ "xtend": "~4.0.0"
}
},
- "node_modules/type-detect": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
- "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "node_modules/through2-filter/node_modules/readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"dev": true,
- "engines": {
- "node": ">=4"
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
}
},
- "node_modules/type-fest": {
- "version": "2.19.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
- "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
- "engines": {
- "node": ">=12.20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node_modules/through2-filter/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "node_modules/through2-filter/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
}
},
- "node_modules/typed-array-buffer": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
- "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
+ "node_modules/through2-filter/node_modules/through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
"dev": true,
"dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-typed-array": "^1.1.14"
- },
- "engines": {
- "node": ">= 0.4"
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
}
},
- "node_modules/typed-array-byte-length": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
- "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
+ "node_modules/timers-ext": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz",
+ "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.8",
- "for-each": "^0.3.3",
- "gopd": "^1.2.0",
- "has-proto": "^1.2.0",
- "is-typed-array": "^1.1.14"
+ "es5-ext": "^0.10.64",
+ "next-tick": "^1.1.0"
},
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=0.12"
}
},
- "node_modules/typed-array-byte-offset": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
- "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
+ "node_modules/timers-ext/node_modules/es5-ext": {
+ "version": "0.10.64",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
+ "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
"dev": true,
+ "hasInstallScript": true,
"dependencies": {
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "for-each": "^0.3.3",
- "gopd": "^1.2.0",
- "has-proto": "^1.2.0",
- "is-typed-array": "^1.1.15",
- "reflect.getprototypeof": "^1.0.9"
+ "es6-iterator": "^2.0.3",
+ "es6-symbol": "^3.1.3",
+ "esniff": "^2.0.1",
+ "next-tick": "^1.1.0"
},
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=0.10"
}
},
- "node_modules/typed-array-length": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
- "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
+ "node_modules/timers-ext/node_modules/next-tick": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
+ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
+ "dev": true
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "dev": true
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "call-bind": "^1.0.7",
- "for-each": "^0.3.3",
- "gopd": "^1.0.1",
- "is-typed-array": "^1.1.13",
- "possible-typed-array-names": "^1.0.0",
- "reflect.getprototypeof": "^1.0.6"
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=12.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/SuperchupuDev"
}
},
- "node_modules/typescript": {
- "version": "5.7.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
- "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/typograf": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/typograf/-/typograf-7.4.1.tgz",
- "integrity": "sha512-V+1jqkv574pzPTW/JcqkhXQzmA7U3B2xVKc6QMzNxmN+1eecVn164Z8Wm8xM6ArKSk/sUjKvOPoT0U3G6zOMjQ==",
+ "license": "MIT",
"engines": {
- "node": ">= 4"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/uc.micro": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
- "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
"dev": true
},
- "node_modules/unbox-primitive": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
- "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
+ "node_modules/to-absolute-glob": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz",
+ "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==",
"dev": true,
"dependencies": {
- "call-bound": "^1.0.3",
- "has-bigints": "^1.0.2",
- "has-symbols": "^1.1.0",
- "which-boxed-primitive": "^1.1.1"
+ "is-absolute": "^1.0.0",
+ "is-negated-glob": "^1.0.0"
},
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=0.10.0"
}
},
- "node_modules/unc-path-regex": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
- "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==",
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8.0"
}
},
- "node_modules/undertaker": {
+ "node_modules/to-through": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz",
- "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==",
+ "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz",
+ "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==",
"dev": true,
"dependencies": {
- "bach": "^2.0.1",
- "fast-levenshtein": "^3.0.0",
- "last-run": "^2.0.0",
- "undertaker-registry": "^2.0.0"
+ "through2": "^2.0.3"
},
"engines": {
- "node": ">=10.13.0"
+ "node": ">= 0.10"
}
},
- "node_modules/undertaker-registry": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz",
- "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==",
+ "node_modules/to-through/node_modules/readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"dev": true,
- "engines": {
- "node": ">= 10.13.0"
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
}
},
- "node_modules/undertaker/node_modules/fast-levenshtein": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz",
- "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==",
+ "node_modules/to-through/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "node_modules/to-through/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/to-through/node_modules/through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
"dev": true,
"dependencies": {
- "fastest-levenshtein": "^1.0.7"
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
}
},
- "node_modules/undici-types": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
- "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "node_modules/toggle-selection": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
+ "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
"dev": true
},
- "node_modules/unicode-canonical-property-names-ecmascript": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
- "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
+ "node_modules/tough-cookie": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
+ "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
"dev": true,
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
"engines": {
- "node": ">=4"
+ "node": ">=6"
}
},
- "node_modules/unicode-match-property-ecmascript": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
- "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
+ "node_modules/tough-cookie/node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"dev": true,
- "dependencies": {
- "unicode-canonical-property-names-ecmascript": "^2.0.0",
- "unicode-property-aliases-ecmascript": "^2.0.0"
- },
"engines": {
- "node": ">=4"
+ "node": ">= 4.0.0"
}
},
- "node_modules/unicode-match-property-value-ecmascript": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz",
- "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==",
+ "node_modules/trim-newlines": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz",
+ "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==",
"dev": true,
"engines": {
- "node": ">=4"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/unicode-property-aliases-ecmascript": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
- "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==",
+ "node_modules/trough": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
"dev": true,
- "engines": {
- "node": ">=4"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/unicorn-magic": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
- "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
+ "node_modules/ts-api-utils": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
+ "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
"dev": true,
"engines": {
- "node": ">=18"
+ "node": ">=16"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "peerDependencies": {
+ "typescript": ">=4.2.0"
}
},
- "node_modules/unified": {
- "version": "11.0.5",
- "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
- "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
+ "node_modules/ts-dedent": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
+ "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.10"
+ }
+ },
+ "node_modules/ts-jest": {
+ "version": "29.2.5",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz",
+ "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==",
"dev": true,
"dependencies": {
- "@types/unist": "^3.0.0",
- "bail": "^2.0.0",
- "devlop": "^1.0.0",
- "extend": "^3.0.0",
- "is-plain-obj": "^4.0.0",
- "trough": "^2.0.0",
- "vfile": "^6.0.0"
+ "bs-logger": "^0.2.6",
+ "ejs": "^3.1.10",
+ "fast-json-stable-stringify": "^2.1.0",
+ "jest-util": "^29.0.0",
+ "json5": "^2.2.3",
+ "lodash.memoize": "^4.1.2",
+ "make-error": "^1.3.6",
+ "semver": "^7.6.3",
+ "yargs-parser": "^21.1.1"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/transform": "^29.0.0",
+ "@jest/types": "^29.0.0",
+ "babel-jest": "^29.0.0",
+ "jest": "^29.0.0",
+ "typescript": ">=4.3 <6"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/transform": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ }
}
},
- "node_modules/unified/node_modules/is-plain-obj": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
- "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "node_modules/ts-jest/node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"engines": {
"node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/unique-stream": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz",
- "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==",
+ "node_modules/tsconfig-paths": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
"dev": true,
"dependencies": {
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "through2-filter": "^3.0.0"
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
}
},
- "node_modules/unist-util-is": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
- "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
+ "node_modules/tsconfig-paths/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"dependencies": {
- "@types/unist": "^3.0.0"
+ "minimist": "^1.2.0"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
+ "bin": {
+ "json5": "lib/cli.js"
}
},
- "node_modules/unist-util-stringify-position": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
- "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
+ "node_modules/tsutils": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
"dev": true,
"dependencies": {
- "@types/unist": "^3.0.0"
+ "tslib": "^1.8.1"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
+ "engines": {
+ "node": ">= 6"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
}
},
- "node_modules/unist-util-visit": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
- "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
+ "node_modules/tsutils/node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ },
+ "node_modules/type": {
+ "version": "2.7.3",
+ "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
+ "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==",
+ "dev": true
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true,
"dependencies": {
- "@types/unist": "^3.0.0",
- "unist-util-is": "^6.0.0",
- "unist-util-visit-parents": "^6.0.0"
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "engines": {
+ "node": ">=12.20"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/unist-util-visit-parents": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
- "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
"dev": true,
"dependencies": {
- "@types/unist": "^3.0.0",
- "unist-util-is": "^6.0.0"
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.14"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
+ "engines": {
+ "node": ">= 0.4"
}
},
- "node_modules/universal-cookie": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.2.tgz",
- "integrity": "sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ==",
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
+ "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
+ "dev": true,
"dependencies": {
- "@types/cookie": "^0.6.0",
- "cookie": "^0.7.2"
- }
- },
- "node_modules/universal-cookie/node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.14"
+ },
"engines": {
- "node": ">= 0.6"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/universalify": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
- "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
+ "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
"dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.15",
+ "reflect.getprototypeof": "^1.0.9"
+ },
"engines": {
- "node": ">= 10.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/unplugin": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz",
- "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==",
+ "node_modules/typed-array-length": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
+ "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
"dev": true,
"dependencies": {
- "acorn": "^8.14.0",
- "webpack-virtual-modules": "^0.6.2"
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0",
+ "reflect.getprototypeof": "^1.0.6"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/update-browserslist-db": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
- "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
+ "node_modules/typescript": {
+ "version": "5.7.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
+ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
"bin": {
- "update-browserslist-db": "cli.js"
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
},
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
+ "engines": {
+ "node": ">=14.17"
}
},
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dependencies": {
- "punycode": "^2.1.0"
+ "node_modules/typograf": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/typograf/-/typograf-7.4.1.tgz",
+ "integrity": "sha512-V+1jqkv574pzPTW/JcqkhXQzmA7U3B2xVKc6QMzNxmN+1eecVn164Z8Wm8xM6ArKSk/sUjKvOPoT0U3G6zOMjQ==",
+ "engines": {
+ "node": ">= 4"
}
},
- "node_modules/urix": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
- "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==",
- "deprecated": "Please see https://github.com/lydell/urix#deprecated",
+ "node_modules/uc.micro": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
+ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
"dev": true
},
- "node_modules/url": {
- "version": "0.11.4",
- "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz",
- "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==",
+ "node_modules/unbox-primitive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
+ "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
"dev": true,
"dependencies": {
- "punycode": "^1.4.1",
- "qs": "^6.12.3"
+ "call-bound": "^1.0.3",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "which-boxed-primitive": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/url-parse": {
- "version": "1.5.10",
- "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
- "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "node_modules/unc-path-regex": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
+ "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==",
"dev": true,
- "dependencies": {
- "querystringify": "^2.1.1",
- "requires-port": "^1.0.0"
+ "engines": {
+ "node": ">=0.10.0"
}
},
- "node_modules/url/node_modules/punycode": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
- "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
- "dev": true
- },
- "node_modules/use-isomorphic-layout-effect": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
- "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
+ "node_modules/undertaker": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz",
+ "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==",
"dev": true,
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ "dependencies": {
+ "bach": "^2.0.1",
+ "fast-levenshtein": "^3.0.0",
+ "last-run": "^2.0.0",
+ "undertaker-registry": "^2.0.0"
},
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
+ "engines": {
+ "node": ">=10.13.0"
}
},
- "node_modules/use-memo-one": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz",
- "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==",
+ "node_modules/undertaker-registry": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz",
+ "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==",
"dev": true,
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ "engines": {
+ "node": ">= 10.13.0"
}
},
- "node_modules/util": {
- "version": "0.12.5",
- "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
- "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
+ "node_modules/undertaker/node_modules/fast-levenshtein": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz",
+ "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==",
"dev": true,
"dependencies": {
- "inherits": "^2.0.3",
- "is-arguments": "^1.0.4",
- "is-generator-function": "^1.0.7",
- "is-typed-array": "^1.1.3",
- "which-typed-array": "^1.1.2"
+ "fastest-levenshtein": "^1.0.7"
}
},
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true
- },
- "node_modules/utila": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
- "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==",
+ "node_modules/undici-types": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true
},
- "node_modules/utility-types": {
- "version": "3.10.0",
- "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz",
- "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/uuid": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
- "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
- "bin": {
- "uuid": "dist/bin/uuid"
+ "node_modules/unicode-canonical-property-names-ecmascript": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
+ "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
}
},
- "node_modules/v8-to-istanbul": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz",
- "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==",
+ "node_modules/unicode-match-property-ecmascript": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
+ "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
"dev": true,
"dependencies": {
- "@jridgewell/trace-mapping": "^0.3.12",
- "@types/istanbul-lib-coverage": "^2.0.1",
- "convert-source-map": "^1.6.0"
+ "unicode-canonical-property-names-ecmascript": "^2.0.0",
+ "unicode-property-aliases-ecmascript": "^2.0.0"
},
"engines": {
- "node": ">=10.12.0"
+ "node": ">=4"
}
},
- "node_modules/validate-npm-package-license": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
- "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "node_modules/unicode-match-property-value-ecmascript": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz",
+ "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==",
"dev": true,
- "dependencies": {
- "spdx-correct": "^3.0.0",
- "spdx-expression-parse": "^3.0.0"
+ "engines": {
+ "node": ">=4"
}
},
- "node_modules/value-or-function": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz",
- "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==",
+ "node_modules/unicode-property-aliases-ecmascript": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
+ "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==",
"dev": true,
"engines": {
- "node": ">= 0.10"
+ "node": ">=4"
}
},
- "node_modules/vfile": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
- "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "node_modules/unicorn-magic": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
+ "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
"dev": true,
- "dependencies": {
- "@types/unist": "^3.0.0",
- "vfile-message": "^4.0.0"
+ "engines": {
+ "node": ">=18"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/vfile-message": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
- "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
+ "node_modules/unified": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
"dev": true,
"dependencies": {
"@types/unist": "^3.0.0",
- "unist-util-stringify-position": "^4.0.0"
+ "bail": "^2.0.0",
+ "devlop": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-plain-obj": "^4.0.0",
+ "trough": "^2.0.0",
+ "vfile": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
- "node_modules/vinyl": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz",
- "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==",
+ "node_modules/unified/node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
"dev": true,
- "dependencies": {
- "clone": "^2.1.1",
- "clone-buffer": "^1.0.0",
- "clone-stats": "^1.0.0",
- "cloneable-readable": "^1.0.0",
- "remove-trailing-separator": "^1.0.1",
- "replace-ext": "^1.0.0"
- },
"engines": {
- "node": ">= 0.10"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/vinyl-contents": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz",
- "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==",
+ "node_modules/unique-stream": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz",
+ "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==",
"dev": true,
"dependencies": {
- "bl": "^5.0.0",
- "vinyl": "^3.0.0"
- },
- "engines": {
- "node": ">=10.13.0"
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "through2-filter": "^3.0.0"
}
},
- "node_modules/vinyl-contents/node_modules/bl": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
- "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==",
+ "node_modules/unist-util-is": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
+ "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
"dev": true,
"dependencies": {
- "buffer": "^6.0.3",
- "inherits": "^2.0.4",
- "readable-stream": "^3.4.0"
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/vinyl-contents/node_modules/replace-ext": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz",
- "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==",
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
"dev": true,
- "engines": {
- "node": ">= 10"
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/vinyl-contents/node_modules/vinyl": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz",
- "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==",
+ "node_modules/unist-util-visit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
+ "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
"dev": true,
"dependencies": {
- "clone": "^2.1.2",
- "clone-stats": "^1.0.0",
- "remove-trailing-separator": "^1.1.0",
- "replace-ext": "^2.0.0",
- "teex": "^1.0.1"
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
},
- "engines": {
- "node": ">=10.13.0"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/vinyl-fs": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz",
- "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==",
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
+ "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
"dev": true,
"dependencies": {
- "fs-mkdirp-stream": "^1.0.0",
- "glob-stream": "^6.1.0",
- "graceful-fs": "^4.0.0",
- "is-valid-glob": "^1.0.0",
- "lazystream": "^1.0.0",
- "lead": "^1.0.0",
- "object.assign": "^4.0.4",
- "pumpify": "^1.3.5",
- "readable-stream": "^2.3.3",
- "remove-bom-buffer": "^3.0.0",
- "remove-bom-stream": "^1.2.0",
- "resolve-options": "^1.1.0",
- "through2": "^2.0.0",
- "to-through": "^2.0.0",
- "value-or-function": "^3.0.0",
- "vinyl": "^2.0.0",
- "vinyl-sourcemap": "^1.1.0"
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
},
- "engines": {
- "node": ">= 0.10"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/vinyl-fs/node_modules/readable-stream": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
- "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
- "dev": true,
+ "node_modules/universal-cookie": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.2.tgz",
+ "integrity": "sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ==",
"dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
+ "@types/cookie": "^0.6.0",
+ "cookie": "^0.7.2"
}
},
- "node_modules/vinyl-fs/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
+ "node_modules/universal-cookie/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
},
- "node_modules/vinyl-fs/node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "node_modules/universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/unplugin": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz",
+ "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==",
"dev": true,
"dependencies": {
- "safe-buffer": "~5.1.0"
+ "acorn": "^8.14.0",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
}
},
- "node_modules/vinyl-fs/node_modules/through2": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
- "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
+ "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
"dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"dependencies": {
- "readable-stream": "~2.3.6",
- "xtend": "~4.0.1"
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
}
},
- "node_modules/vinyl-sourcemap": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz",
- "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==",
- "dev": true,
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dependencies": {
- "append-buffer": "^1.0.2",
- "convert-source-map": "^1.5.0",
- "graceful-fs": "^4.1.6",
- "normalize-path": "^2.1.1",
- "now-and-later": "^2.0.0",
- "remove-bom-buffer": "^3.0.0",
- "vinyl": "^2.0.0"
- },
- "engines": {
- "node": ">= 0.10"
+ "punycode": "^2.1.0"
}
},
- "node_modules/vinyl-sourcemap/node_modules/normalize-path": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
- "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==",
+ "node_modules/urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==",
+ "deprecated": "Please see https://github.com/lydell/urix#deprecated",
+ "dev": true
+ },
+ "node_modules/url": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz",
+ "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==",
"dev": true,
"dependencies": {
- "remove-trailing-separator": "^1.0.1"
+ "punycode": "^1.4.1",
+ "qs": "^6.12.3"
},
"engines": {
- "node": ">=0.10.0"
+ "node": ">= 0.4"
}
},
- "node_modules/vinyl-sourcemaps-apply": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz",
- "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==",
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"dev": true,
"dependencies": {
- "source-map": "^0.5.1"
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
}
},
- "node_modules/vite": {
- "version": "5.4.16",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.16.tgz",
- "integrity": "sha512-Y5gnfp4NemVfgOTDQAunSD4346fal44L9mszGGY/e+qxsRT5y1sMlS/8tiQ8AFAp+MFgYNSINdfEchJiPm41vQ==",
+ "node_modules/url/node_modules/punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
+ "dev": true
+ },
+ "node_modules/use-isomorphic-layout-effect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
+ "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
"dev": true,
- "dependencies": {
- "esbuild": "^0.21.3",
- "postcss": "^8.4.43",
- "rollup": "^4.20.0"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
"peerDependencies": {
- "@types/node": "^18.0.0 || >=20.0.0",
- "less": "*",
- "lightningcss": "^1.21.0",
- "sass": "*",
- "sass-embedded": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.4.0"
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
+ "@types/react": {
"optional": true
}
}
},
- "node_modules/vite-plugin-commonjs": {
- "version": "0.10.1",
- "resolved": "https://registry.npmjs.org/vite-plugin-commonjs/-/vite-plugin-commonjs-0.10.1.tgz",
- "integrity": "sha512-taP8R9kYGlCW5OzkVR0UIWRCnG6rSxeWWuA7tnU5b9t5MniibOnDY219NhisTeDhJAeGT8cEnrhVWZ9A5yD+vg==",
+ "node_modules/use-memo-one": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz",
+ "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==",
"dev": true,
- "dependencies": {
- "acorn": "^8.8.2",
- "fast-glob": "^3.2.12",
- "magic-string": "^0.30.1",
- "vite-plugin-dynamic-import": "^1.5.0"
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
- "node_modules/vite-plugin-dynamic-import": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/vite-plugin-dynamic-import/-/vite-plugin-dynamic-import-1.5.0.tgz",
- "integrity": "sha512-Qp85c+AVJmLa8MLni74U4BDiWpUeFNx7NJqbGZyR2XJOU7mgW0cb7nwlAMucFyM4arEd92Nfxp4j44xPi6Fu7g==",
- "dev": true,
- "dependencies": {
- "acorn": "^8.8.2",
- "es-module-lexer": "^1.2.1",
- "fast-glob": "^3.2.12",
- "magic-string": "^0.30.1"
+ "node_modules/use-sync-external-store": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
+ "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
- "node_modules/vite-plugin-svgr": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz",
- "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==",
+ "node_modules/util": {
+ "version": "0.12.5",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
+ "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
"dev": true,
"dependencies": {
- "@rollup/pluginutils": "^5.0.5",
- "@svgr/core": "^8.1.0",
- "@svgr/plugin-jsx": "^8.1.0"
- },
- "peerDependencies": {
- "vite": "^2.6.0 || 3 || 4 || 5"
+ "inherits": "^2.0.3",
+ "is-arguments": "^1.0.4",
+ "is-generator-function": "^1.0.7",
+ "is-typed-array": "^1.1.3",
+ "which-typed-array": "^1.1.2"
}
},
- "node_modules/vite/node_modules/@esbuild/android-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
- "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/utila": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
+ "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==",
+ "dev": true
+ },
+ "node_modules/utility-types": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz",
+ "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==",
"engines": {
- "node": ">=12"
+ "node": ">= 4"
}
},
- "node_modules/vite/node_modules/@esbuild/android-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
- "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
+ "node_modules/uuid": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+ "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
}
},
- "node_modules/vite/node_modules/@esbuild/android-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
- "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
- "cpu": [
- "x64"
- ],
+ "node_modules/v8-to-istanbul": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz",
+ "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==",
"dev": true,
- "optional": true,
- "os": [
- "android"
- ],
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^1.6.0"
+ },
"engines": {
- "node": ">=12"
+ "node": ">=10.12.0"
}
},
- "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
- "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
}
},
- "node_modules/vite/node_modules/@esbuild/darwin-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
- "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
- "cpu": [
- "x64"
- ],
+ "node_modules/value-or-function": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz",
+ "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==",
"dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
"engines": {
- "node": ">=12"
+ "node": ">= 0.10"
}
},
- "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
- "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
"dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
- "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
- "cpu": [
- "x64"
- ],
+ "node_modules/vfile-message": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
+ "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
"dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/vite/node_modules/@esbuild/linux-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
- "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
- "cpu": [
- "arm"
- ],
+ "node_modules/vinyl": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz",
+ "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==",
"dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "clone": "^2.1.1",
+ "clone-buffer": "^1.0.0",
+ "clone-stats": "^1.0.0",
+ "cloneable-readable": "^1.0.0",
+ "remove-trailing-separator": "^1.0.1",
+ "replace-ext": "^1.0.0"
+ },
"engines": {
- "node": ">=12"
+ "node": ">= 0.10"
}
},
- "node_modules/vite/node_modules/@esbuild/linux-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
- "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/vinyl-contents": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz",
+ "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==",
"dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "bl": "^5.0.0",
+ "vinyl": "^3.0.0"
+ },
"engines": {
- "node": ">=12"
+ "node": ">=10.13.0"
}
},
- "node_modules/vite/node_modules/@esbuild/linux-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
- "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
- "cpu": [
- "ia32"
- ],
+ "node_modules/vinyl-contents/node_modules/bl": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
+ "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==",
"dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
}
},
- "node_modules/vite/node_modules/@esbuild/linux-loong64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
- "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
- "cpu": [
- "loong64"
- ],
+ "node_modules/vinyl-contents/node_modules/replace-ext": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz",
+ "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==",
"dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
"engines": {
- "node": ">=12"
+ "node": ">= 10"
}
},
- "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
- "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
- "cpu": [
- "mips64el"
- ],
+ "node_modules/vinyl-contents/node_modules/vinyl": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz",
+ "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==",
"dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "clone": "^2.1.2",
+ "clone-stats": "^1.0.0",
+ "remove-trailing-separator": "^1.1.0",
+ "replace-ext": "^2.0.0",
+ "teex": "^1.0.1"
+ },
"engines": {
- "node": ">=12"
+ "node": ">=10.13.0"
}
},
- "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
- "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
- "cpu": [
- "ppc64"
- ],
+ "node_modules/vinyl-fs": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz",
+ "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==",
"dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "fs-mkdirp-stream": "^1.0.0",
+ "glob-stream": "^6.1.0",
+ "graceful-fs": "^4.0.0",
+ "is-valid-glob": "^1.0.0",
+ "lazystream": "^1.0.0",
+ "lead": "^1.0.0",
+ "object.assign": "^4.0.4",
+ "pumpify": "^1.3.5",
+ "readable-stream": "^2.3.3",
+ "remove-bom-buffer": "^3.0.0",
+ "remove-bom-stream": "^1.2.0",
+ "resolve-options": "^1.1.0",
+ "through2": "^2.0.0",
+ "to-through": "^2.0.0",
+ "value-or-function": "^3.0.0",
+ "vinyl": "^2.0.0",
+ "vinyl-sourcemap": "^1.1.0"
+ },
"engines": {
- "node": ">=12"
+ "node": ">= 0.10"
}
},
- "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
- "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
- "cpu": [
- "riscv64"
- ],
+ "node_modules/vinyl-fs/node_modules/readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
}
},
- "node_modules/vite/node_modules/@esbuild/linux-s390x": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
- "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
- "cpu": [
- "s390x"
- ],
+ "node_modules/vinyl-fs/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "node_modules/vinyl-fs/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
}
},
- "node_modules/vite/node_modules/@esbuild/linux-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
- "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
- "cpu": [
- "x64"
- ],
+ "node_modules/vinyl-fs/node_modules/through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
"dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
}
},
- "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
- "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
- "cpu": [
- "x64"
- ],
+ "node_modules/vinyl-sourcemap": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz",
+ "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==",
"dev": true,
- "optional": true,
- "os": [
- "netbsd"
- ],
+ "dependencies": {
+ "append-buffer": "^1.0.2",
+ "convert-source-map": "^1.5.0",
+ "graceful-fs": "^4.1.6",
+ "normalize-path": "^2.1.1",
+ "now-and-later": "^2.0.0",
+ "remove-bom-buffer": "^3.0.0",
+ "vinyl": "^2.0.0"
+ },
"engines": {
- "node": ">=12"
+ "node": ">= 0.10"
}
},
- "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
- "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
- "cpu": [
- "x64"
- ],
+ "node_modules/vinyl-sourcemap/node_modules/normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==",
"dev": true,
- "optional": true,
- "os": [
- "openbsd"
- ],
+ "dependencies": {
+ "remove-trailing-separator": "^1.0.1"
+ },
"engines": {
- "node": ">=12"
+ "node": ">=0.10.0"
}
},
- "node_modules/vite/node_modules/@esbuild/sunos-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
- "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
- "cpu": [
- "x64"
- ],
+ "node_modules/vinyl-sourcemaps-apply": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz",
+ "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==",
"dev": true,
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "source-map": "^0.5.1"
}
},
- "node_modules/vite/node_modules/@esbuild/win32-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
- "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/vite": {
+ "version": "6.3.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
+ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
"engines": {
- "node": ">=12"
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
}
},
- "node_modules/vite/node_modules/@esbuild/win32-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
- "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
- "cpu": [
- "ia32"
- ],
+ "node_modules/vite-plugin-commonjs": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/vite-plugin-commonjs/-/vite-plugin-commonjs-0.10.1.tgz",
+ "integrity": "sha512-taP8R9kYGlCW5OzkVR0UIWRCnG6rSxeWWuA7tnU5b9t5MniibOnDY219NhisTeDhJAeGT8cEnrhVWZ9A5yD+vg==",
"dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "acorn": "^8.8.2",
+ "fast-glob": "^3.2.12",
+ "magic-string": "^0.30.1",
+ "vite-plugin-dynamic-import": "^1.5.0"
}
},
- "node_modules/vite/node_modules/@esbuild/win32-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
- "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
- "cpu": [
- "x64"
- ],
+ "node_modules/vite-plugin-dynamic-import": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-dynamic-import/-/vite-plugin-dynamic-import-1.5.0.tgz",
+ "integrity": "sha512-Qp85c+AVJmLa8MLni74U4BDiWpUeFNx7NJqbGZyR2XJOU7mgW0cb7nwlAMucFyM4arEd92Nfxp4j44xPi6Fu7g==",
"dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "acorn": "^8.8.2",
+ "es-module-lexer": "^1.2.1",
+ "fast-glob": "^3.2.12",
+ "magic-string": "^0.30.1"
}
},
- "node_modules/vite/node_modules/esbuild": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
- "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "node_modules/vite-plugin-svgr": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.3.0.tgz",
+ "integrity": "sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==",
"dev": true,
- "hasInstallScript": true,
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.1.3",
+ "@svgr/core": "^8.1.0",
+ "@svgr/plugin-jsx": "^8.1.0"
},
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.21.5",
- "@esbuild/android-arm": "0.21.5",
- "@esbuild/android-arm64": "0.21.5",
- "@esbuild/android-x64": "0.21.5",
- "@esbuild/darwin-arm64": "0.21.5",
- "@esbuild/darwin-x64": "0.21.5",
- "@esbuild/freebsd-arm64": "0.21.5",
- "@esbuild/freebsd-x64": "0.21.5",
- "@esbuild/linux-arm": "0.21.5",
- "@esbuild/linux-arm64": "0.21.5",
- "@esbuild/linux-ia32": "0.21.5",
- "@esbuild/linux-loong64": "0.21.5",
- "@esbuild/linux-mips64el": "0.21.5",
- "@esbuild/linux-ppc64": "0.21.5",
- "@esbuild/linux-riscv64": "0.21.5",
- "@esbuild/linux-s390x": "0.21.5",
- "@esbuild/linux-x64": "0.21.5",
- "@esbuild/netbsd-x64": "0.21.5",
- "@esbuild/openbsd-x64": "0.21.5",
- "@esbuild/sunos-x64": "0.21.5",
- "@esbuild/win32-arm64": "0.21.5",
- "@esbuild/win32-ia32": "0.21.5",
- "@esbuild/win32-x64": "0.21.5"
+ "peerDependencies": {
+ "vite": ">=2.6.0"
}
},
"node_modules/vite/node_modules/fsevents": {
@@ -25184,6 +25871,19 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/w3c-xmlserializer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
@@ -25778,6 +26478,33 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/zustand": {
+ "version": "4.5.6",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz",
+ "integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
diff --git a/package.json b/package.json
index 951dc7604..fcbe01300 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@gravity-ui/page-constructor",
- "version": "6.8.1",
+ "version": "6.3.2-alpha.12",
"description": "Gravity UI Page Constructor",
"license": "MIT",
"type": "commonjs",
@@ -24,6 +24,21 @@
"require": "./build/cjs/editor/index.js",
"import": "./build/esm/editor/index.js"
},
+ "./editor-v2": {
+ "types": "./build/esm/editor-v2/index.d.ts",
+ "require": "./build/cjs/editor-v2/index.js",
+ "import": "./build/esm/editor-v2/index.js"
+ },
+ "./form-generator": {
+ "types": "./build/esm/form-generator/index.d.ts",
+ "require": "./build/cjs/form-generator/index.js",
+ "import": "./build/esm/form-generator/index.js"
+ },
+ "./form-builder": {
+ "types": "./build/esm/form-builder/index.d.ts",
+ "require": "./build/cjs/form-builder/index.js",
+ "import": "./build/esm/form-builder/index.js"
+ },
"./server": {
"types": "./server/index.d.ts",
"require": "./server/index.js",
@@ -44,6 +59,15 @@
"editor": [
"./build/esm/editor/index.d.ts"
],
+ "editor-v2": [
+ "./build/esm/editor-v2/index.d.ts"
+ ],
+ "form-generator": [
+ "./build/esm/form-generator/index.d.ts"
+ ],
+ "form-builder": [
+ "./build/esm/form-builder/index.d.ts"
+ ],
"server": [
"./server/index.d.ts"
]
@@ -73,6 +97,7 @@
"lint": "run-p lint:js lint:styles lint:prettier typecheck",
"typecheck": "tsc --noEmit",
"dev": "npm run storybook:start",
+ "dev:playground": "cd playground && vite -c vite.config.mts",
"storybook:start": "storybook dev -p 7009",
"storybook:build": "storybook build -c .storybook -o storybook-static",
"start": "node dist",
@@ -82,6 +107,7 @@
"build:widget": "webpack --config widget.webpack.js",
"build:schema": "webpack --config schema.webpack.js",
"build": "run-p build:client build:server build:widget build:schema",
+ "build:playground": "cd playground && tsc && vite build -c vite.config.mts",
"prepublishOnly": "npm run lint && npm run build",
"prepare": "husky install",
"test": "jest",
@@ -103,14 +129,17 @@
"@react-spring/web": "^9.7.3",
"ajv": "^8.12.0",
"ajv-keywords": "^5.1.0",
+ "deep-object-diff": "^1.1.9",
"final-form": "^4.20.9",
"github-buttons": "2.23.0",
+ "immutable": "^4.3.7",
"js-yaml-source-map": "^0.2.2",
"lodash": "^4.17.21",
"monaco-editor": "^0.38.0",
"react-final-form": "^6.5.9",
"react-monaco-editor": "^0.53.0",
"react-player": "^2.9.0",
+ "react-resizable-panels": "^2.1.3",
"react-slick": "^0.29.0",
"react-transition-group": "^4.4.2",
"react-waypoint": "^10.1.0",
@@ -119,7 +148,8 @@
"swiper": "^6.8.4",
"typograf": "^7.4.1",
"utility-types": "^3.10.0",
- "uuid": "^9.0.0"
+ "uuid": "^9.0.0",
+ "zustand": "^4.5.2"
},
"peerDependencies": {
"@diplodoc/transform": "^4.10.4",
@@ -169,10 +199,13 @@
"@types/uuid": "^9.0.0",
"@types/webpack-env": "^1.18.1",
"@types/youtube-player": "^5.5.11",
+ "@vitejs/plugin-react": "^4.5.0",
"autoprefixer": "^10.4.14",
"babel-loader": "^8.3.0",
+ "bem-cn-lite": "^4.1.0",
"css-loader": "^5.2.7",
"es5-ext": "0.10.53",
+ "esbuild-sass-plugin": "^3.3.1",
"eslint": "^8.57.1",
"eslint-plugin-no-not-accumulator-reassign": "^0.1.0",
"eslint-plugin-react": "^7.37.4",
@@ -203,6 +236,7 @@
"react": "^18.3.1",
"react-docgen-typescript": "^2.2.2",
"react-dom": "^18.3.1",
+ "react-router": "^7.6.1",
"resolve-url-loader": "^3.1.5",
"rimraf": "^6.0.1",
"sass": "^1.63.6",
@@ -215,8 +249,9 @@
"ts-jest": "^29.2.5",
"tslib": "^2.4.0",
"typescript": "^5.7.3",
+ "vite": "^6.3.5",
"vite-plugin-commonjs": "^0.10.1",
- "vite-plugin-svgr": "^4.2.0",
+ "vite-plugin-svgr": "^4.3.0",
"webpack": "^5.98.0",
"webpack-cli": "^6.0.1",
"webpack-shell-plugin-next": "^2.3.1"
diff --git a/playground/.eslintignore b/playground/.eslintignore
new file mode 100644
index 000000000..dffb6a9ed
--- /dev/null
+++ b/playground/.eslintignore
@@ -0,0 +1,3 @@
+build
+dist
+public
diff --git a/playground/.gitignore b/playground/.gitignore
new file mode 100644
index 000000000..cf74115dd
--- /dev/null
+++ b/playground/.gitignore
@@ -0,0 +1,28 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+/dist
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# typescript
+*.tsbuildinfo
diff --git a/playground/.prettierignore b/playground/.prettierignore
new file mode 100644
index 000000000..dffb6a9ed
--- /dev/null
+++ b/playground/.prettierignore
@@ -0,0 +1,3 @@
+build
+dist
+public
diff --git a/playground/index.html b/playground/index.html
new file mode 100644
index 000000000..be673cfe6
--- /dev/null
+++ b/playground/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+ Page Constructor Playground
+
+
+ You need to enable JavaScript to run this app.
+
+
+
+
diff --git a/playground/public/favicon.ico b/playground/public/favicon.ico
new file mode 100644
index 000000000..74cc5d2cd
Binary files /dev/null and b/playground/public/favicon.ico differ
diff --git a/playground/public/manifest.json b/playground/public/manifest.json
new file mode 100644
index 000000000..c5e41e11a
--- /dev/null
+++ b/playground/public/manifest.json
@@ -0,0 +1,13 @@
+{
+ "short_name": "React App",
+ "name": "Gravity UI – Vite Example",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone"
+}
diff --git a/playground/public/vite.svg b/playground/public/vite.svg
new file mode 100644
index 000000000..e7b8dfb1b
--- /dev/null
+++ b/playground/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/playground/src/main.tsx b/playground/src/main.tsx
new file mode 100644
index 000000000..b87d84bd4
--- /dev/null
+++ b/playground/src/main.tsx
@@ -0,0 +1,17 @@
+import * as React from 'react';
+import ReactDOM from 'react-dom/client';
+import {BrowserRouter} from 'react-router';
+import '@gravity-ui/uikit/styles/fonts.css';
+import '@gravity-ui/uikit/styles/styles.css';
+import './styles/globals.scss';
+
+import Router from './router';
+
+const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
+root.render(
+
+
+
+
+ ,
+);
diff --git a/playground/src/pages/editor/editor.scss b/playground/src/pages/editor/editor.scss
new file mode 100644
index 000000000..69b14306b
--- /dev/null
+++ b/playground/src/pages/editor/editor.scss
@@ -0,0 +1,13 @@
+$block: '.editor';
+
+#{$block} {
+ height: 100vh;
+
+ &__other {
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ align-items: flex-start;
+ }
+}
diff --git a/playground/src/pages/editor/editor.tsx b/playground/src/pages/editor/editor.tsx
new file mode 100644
index 000000000..69196b779
--- /dev/null
+++ b/playground/src/pages/editor/editor.tsx
@@ -0,0 +1,56 @@
+import * as React from 'react';
+import block from 'bem-cn-lite';
+import {useNavigate} from 'react-router';
+import {Button, Text, ThemeProvider} from '@gravity-ui/uikit';
+
+import {Editor} from '../../../../src/editor-v2';
+
+import './editor.scss';
+
+const b = block('editor');
+
+const Other = () => {
+ const navigate = useNavigate();
+
+ return (
+
+ Form Editor
+ navigate('/?page=form')}>Go to form editor
+
+ );
+};
+
+const COMPONENTS_CONFIG = {
+ leftTabs: [
+ {
+ id: 'other',
+ title: 'OTHER',
+ component: Other,
+ },
+ ],
+};
+
+export default function EditorPage() {
+ const [initialUrl, setInitialUrl] = React.useState('');
+
+ React.useEffect(() => {
+ if (typeof window !== 'undefined') {
+ setInitialUrl(window.location.origin + '/?page=pc&id=1');
+ }
+ }, []);
+
+ return (
+
+
+ {initialUrl && (
+ {}}
+ />
+ )}
+
+
+ );
+}
diff --git a/playground/src/pages/form/components/FormOutput/FormOutput.scss b/playground/src/pages/form/components/FormOutput/FormOutput.scss
new file mode 100644
index 000000000..5316679d0
--- /dev/null
+++ b/playground/src/pages/form/components/FormOutput/FormOutput.scss
@@ -0,0 +1,11 @@
+.form-output {
+ &__content {
+ background-color: var(--g-color-base-generic);
+ padding: 12px;
+ border-radius: 6px;
+ border: 1px solid var(--g-color-line-generic);
+ font-family: monospace;
+ font-size: 12px;
+ white-space: pre-wrap;
+ }
+}
diff --git a/playground/src/pages/form/components/FormOutput/FormOutput.tsx b/playground/src/pages/form/components/FormOutput/FormOutput.tsx
new file mode 100644
index 000000000..fc777d563
--- /dev/null
+++ b/playground/src/pages/form/components/FormOutput/FormOutput.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react';
+import {Text} from '@gravity-ui/uikit';
+import block from 'bem-cn-lite';
+
+import './FormOutput.scss';
+
+const b = block('form-output');
+
+interface FormOutputProps {
+ title: string;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ data: any;
+ className?: string;
+}
+
+export const FormOutput: React.FC = ({title, data, className}) => {
+ return (
+
+
+ {title}
+
+
{JSON.stringify(data, null, 2)}
+
+ );
+};
diff --git a/playground/src/pages/form/form.scss b/playground/src/pages/form/form.scss
new file mode 100644
index 000000000..fe6834284
--- /dev/null
+++ b/playground/src/pages/form/form.scss
@@ -0,0 +1,56 @@
+$block: '.form';
+
+#{$block} {
+ width: 100%;
+ height: 100vh;
+ overflow: hidden;
+
+ &__header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 4px;
+ margin-bottom: 16px;
+ }
+
+ &__header-buttons {
+ display: flex;
+ gap: 8px;
+ }
+
+ &__panel-group {
+ width: 100%;
+ height: 100%;
+ }
+
+ &__panel-content {
+ padding: 20px;
+ height: 100%;
+ overflow: auto;
+ background-color: var(--g-color-base-background);
+ box-sizing: border-box;
+ }
+
+ &__resize-handle {
+ background-color: var(--g-color-line-generic);
+ transition: background-color 0.2s;
+
+ &:hover {
+ background-color: var(--g-color-line-generic-hover);
+ }
+
+ &_horizontal {
+ height: 8px;
+ cursor: row-resize;
+ }
+
+ &_vertical {
+ width: 8px;
+ cursor: col-resize;
+ }
+ }
+
+ &__form {
+ margin-top: 20px;
+ }
+}
diff --git a/playground/src/pages/form/form.tsx b/playground/src/pages/form/form.tsx
new file mode 100644
index 000000000..dfe03bf51
--- /dev/null
+++ b/playground/src/pages/form/form.tsx
@@ -0,0 +1,106 @@
+import * as React from 'react';
+import {Button, Text, ThemeProvider} from '@gravity-ui/uikit';
+import block from 'bem-cn-lite';
+import {Panel, PanelGroup, PanelResizeHandle} from 'react-resizable-panels';
+import {useNavigate} from 'react-router';
+import DynamicForm from '../../../../src/form-generator/FormGenerator';
+import {FormOutput} from './components/FormOutput/FormOutput';
+import {FormBuilder, FormField} from '../../../../src/form-builder';
+
+import './form.scss';
+
+const b = block('form');
+
+const FormContent = () => {
+ const [formFields, setFormFields] = React.useState([]);
+ const [contentConfig, setContentConfig] = React.useState({});
+
+ const resetForm = React.useCallback(() => {
+ setFormFields([]);
+ }, []);
+
+ const navigate = useNavigate();
+
+ return (
+
+
+ {/* Left Panel Group */}
+
+
+ {/* Form Builder Panel */}
+
+
+
+
Form Builder
+
+
+ Reset Form
+
+ navigate('/')} size="m">
+ Go to Editor
+
+
+
+
+
+
+
+
+
+ {/* Resize Handle */}
+
+
+ {/* Form Output Panel */}
+
+
+ rest)}
+ />
+
+
+
+
+
+ {/* Resize Handle */}
+
+
+ {/* Right Panel Group */}
+
+
+ {/* Form Preview Panel */}
+
+
+ Form Preview
+
+
+
+
+ {/* Resize Handle */}
+
+
+ {/* Form Output Panel */}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+// Главный компонент формы, оборачивающий содержимое в провайдер
+export default function FormPage() {
+ return (
+
+
+
+ );
+}
diff --git a/playground/src/pages/pc/example-1/content.json b/playground/src/pages/pc/example-1/content.json
new file mode 100644
index 000000000..396e3394e
--- /dev/null
+++ b/playground/src/pages/pc/example-1/content.json
@@ -0,0 +1,335 @@
+{
+ "blocks": [
+ {
+ "type": "header-block",
+ "title": "Yandex Open Source",
+ "description": "Мы в Яндексе верим, что вклад в опенсорс — это вклад в технологическую эволюцию: без открытости, совместной работы и поддержки развитие IT‑индустрии сильно затруднено. Уже много лет мы используем в своих продуктах сторонние открытые технологии, а также делимся собственными и активно вовлекаем в их развитие разработчиков по всему миру.
",
+ "width": "s",
+ "verticalOffset": "l",
+ "offset": "default",
+ "resetPaddings": true,
+ "background": {
+ "image": {
+ "mobile": "https://storage.yandexcloud.net/yandex-opensource/pages/index/yos-index-cover-m.png",
+ "desktop": "https://storage.yandexcloud.net/yandex-opensource/pages/index/yos-index-cover.png"
+ },
+ "color": "#EFF2F8",
+ "fullWidth": false,
+ "fullWidthMedia": true
+ }
+ },
+ {
+ "type": "extended-features-block",
+ "title": {
+ "text": "Почему мы выкладываем наши технологии в открытый доступ?"
+ },
+ "items": [
+ {
+ "title": "Ответственность",
+ "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-01.svg",
+ "text": "Мы верим, что вкладываться в развитие опенсорс‑технологий — это ответственность каждого технологического лидера на рынке. Без опенсорс‑решений не появились бы многие продукты и сервисы не только Яндекса, но и других крупных компаний, и мы хотим отдавать обратно, делиться теми нашими решениями, которые, как мы считаем, принесут реальную пользу.
"
+ },
+ {
+ "title": "Польза для сообщества",
+ "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-02.svg",
+ "text": "Технологии, которые мы разрабатываем, ежедневно помогают нам эффективно решать огромное количество самых разных задач в наших сервисах. Мы знаем, что разработчики вне Яндекса часто сталкиваются с теми же самыми задачами — и верим, что наши технологии могут быть полезны и им.
"
+ },
+ {
+ "title": "Качество сервисов",
+ "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-03.svg",
+ "text": "Для нас важно разрабатывать и использовать только качественные технологические решения. В особенности это касается опенсорса: зная, что наши решения увидят и будут использовать другие, мы уделяем их качеству особое внимание. А уже в открытом доступе у технологии больше шансов развиваться и улучшаться — в том числе, при участии сообщества разработчиков.
"
+ },
+ {
+ "title": "Бизнес‑потенциал",
+ "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-04.svg",
+ "text": "Мы верим, что при условии роста популярности наших решений и спроса на них со стороны сообщества, то, что мы выкладываем в опенсорс, может далее стать для нас бизнесом. То, что мы выкладываем в опенсорс, можно использовать и во внешних коммерческих проектах.
"
+ },
+ {
+ "title": "Поиск талантов",
+ "icon": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/os-index-icon-05.svg",
+ "text": "Мы ценим каждого, кто вкладывается в сторонние опенсорс‑решения или делится с миром своими. Контрибьюторы в наши продукты нам особенно важны: среди них мы ищем и находим тех, кто сможет развивать технологии уже будучи частью команды Яндекса.
"
+ }
+ ]
+ },
+ {
+ "type": "card-layout-block",
+ "animated": false,
+ "title": "Краткая история опенсорса в Яндексе",
+ "description": "С начала истории развития опенсорса в Яндексе мы успели выложить в открытый доступ десятки собственных проектов, использовать в разработке наших продуктов внешние технологии, а также внесли существенный вклад в их развитие.
",
+ "colSizes": {
+ "all": 12,
+ "lg": 3,
+ "md": 4,
+ "sm": 6
+ },
+ "anchor": {
+ "url": "history",
+ "text": "history"
+ },
+ "children": [
+ {
+ "type": "layout-item",
+ "content": {
+ "title": "2010",
+ "text": "Методология веб‑разработки БЭМ (Блок‑Элемент‑Модификатор) выходит в оперсорс
"
+ },
+ "media": {
+ "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-01.png"
+ }
+ },
+ {
+ "type": "layout-item",
+ "content": {
+ "title": "2012",
+ "text": "Запуск Яндекс Браузера на базе Blink (Chromium)
"
+ },
+ "media": {
+ "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-02.png"
+ }
+ },
+ {
+ "type": "layout-item",
+ "content": {
+ "title": "2013",
+ "text": "Яндекс начинает контрибьютить в ядро Linux
"
+ },
+ "media": {
+ "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-03.png"
+ }
+ },
+ {
+ "type": "layout-item",
+ "content": {
+ "title": "2016",
+ "text": "Выход в опенсорс ClickHouse
\\nВыход в опенсорс Hermione (с 2024 года — Testplane)
"
+ },
+ "media": {
+ "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2016.png"
+ }
+ },
+ {
+ "type": "layout-item",
+ "content": {
+ "title": "2017",
+ "text": "Выход в опенсорс CatBoost \\nЯндекс начинает контрибьютить в PostgreSQL
"
+ },
+ "media": {
+ "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-05.png"
+ }
+ },
+ {
+ "type": "layout-item",
+ "content": {
+ "title": "2018",
+ "text": "Выход в опенсорс Одиссея
\\nЯндекс — топ‑контрибьютор в WAL‑G
"
+ },
+ "media": {
+ "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-06.png"
+ }
+ },
+ {
+ "type": "layout-item",
+ "content": {
+ "title": "2019",
+ "text": "В Яндексе появляется команда разработки СУБД с открытым исходным кодом
\\nЯндекс — спонсор разработки PostgreSQL
"
+ },
+ "media": {
+ "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-07.png"
+ }
+ },
+ {
+ "type": "layout-item",
+ "content": {
+ "title": "2020",
+ "text": "Выход в опенсорс Testsuite
"
+ },
+ "media": {
+ "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2020.png"
+ }
+ },
+ {
+ "type": "layout-item",
+ "content": {
+ "title": "2022",
+ "text": "Яндекс — один из основных спонсоров разработки PostgreSQL
\\nВыход в опенсорс YDB, userver, YaLM 100B, DivKit, Yatagan
\\nСтарт программы «Код для всех»
"
+ },
+ "media": {
+ "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-09.png"
+ }
+ },
+ {
+ "type": "layout-item",
+ "content": {
+ "title": "2023",
+ "text": "Выход в опенсорс YTsaurus, Gravity UI, AppMetrica, Diplodoc, DataLens и счётчика Метрики
\\nСтарт Программы грантов Yandex Open Source
"
+ },
+ "media": {
+ "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2023.png"
+ }
+ },
+ {
+ "type": "layout-item",
+ "content": {
+ "title": "2024",
+ "text": "Первый Yandex Open Source Jam
\\nВыход в опенсорс YaFSDP
"
+ },
+ "media": {
+ "image": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-timeline-2024.png"
+ }
+ }
+ ]
+ },
+ {
+ "type": "card-layout-block",
+ "title": "Наши проекты",
+ "description": "В Яндексе мы разрабатываем и развиваем технологические решения самых разных сфер применения, размеров и сложности. Поэтому и в открытый доступ попадают самые разные проекты — главное, чтобы они приносили пользу не только нам, но и другим.
",
+ "colSizes": {
+ "all": 12,
+ "lg": 3,
+ "md": 4,
+ "sm": 6
+ },
+ "anchor": {
+ "url": "projects",
+ "text": "projects"
+ },
+ "children": [
+ {
+ "type": "background-card",
+ "title": "YDB",
+ "text": "Отказоустойчивая распределённая SQL база данных
",
+ "backgroundColor": "#2399FF",
+ "background": {
+ "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-ydb.png",
+ "alt": "card-background"
+ },
+ "paddingBottom": "m",
+ "theme": "dark"
+ },
+ {
+ "type": "background-card",
+ "title": "YTsaurus",
+ "text": "Платформа для хранения и обработки больших данных
",
+ "backgroundColor": "#FFB23E",
+ "background": {
+ "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-yt.png",
+ "alt": "card-background"
+ },
+ "paddingBottom": "m",
+ "theme": "light"
+ },
+ {
+ "type": "background-card",
+ "title": "GravityUI",
+ "text": "Библиотеки для создания интерфейсов
",
+ "backgroundColor": "#262626",
+ "background": {
+ "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-gui.png",
+ "alt": "card-background"
+ },
+ "paddingBottom": "m",
+ "theme": "dark"
+ },
+ {
+ "type": "background-card",
+ "title": "DivKit",
+ "text": "Фреймворк для server‑driven интерфейсов
",
+ "backgroundColor": "#F1F1F1",
+ "background": {
+ "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-dk.png",
+ "alt": "card-background"
+ },
+ "paddingBottom": "m",
+ "theme": "light"
+ },
+ {
+ "type": "background-card",
+ "title": "Diplodoc",
+ "text": "Платформа для написания документации в концепции Docs as Code
",
+ "backgroundColor": "#79F985",
+ "background": {
+ "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-dd.png",
+ "alt": "card-background"
+ },
+ "paddingBottom": "m",
+ "theme": "light"
+ },
+ {
+ "type": "background-card",
+ "title": "userver",
+ "text": "Фреймворк для создания высоконагруженных приложений
",
+ "backgroundColor": "#FF9D73",
+ "background": {
+ "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-u.png",
+ "alt": "card-background"
+ },
+ "paddingBottom": "m",
+ "theme": "light"
+ },
+ {
+ "type": "background-card",
+ "title": "DataLens",
+ "text": "BI-платформа для анализа и визуализации данных
",
+ "backgroundColor": "#FF7132",
+ "background": {
+ "src": "https://storage.yandexcloud.net/yandex-opensource/pages/index/os-index-card-dl.png",
+ "alt": "card-background"
+ },
+ "paddingBottom": "m",
+ "theme": "dark"
+ },
+ {
+ "type": "basic-card",
+ "title": "И это ещё не всё",
+ "text": "Узнать про наши опенсорс‑проекты больше вы можете на другой странице
",
+ "border": "none",
+ "buttons": [
+ {
+ "text": "Все проекты",
+ "primary": true,
+ "theme": "monochrome",
+ "size": "promo",
+ "url": "/projects"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "largeMedia": true,
+ "mediaOnly": false,
+ "size": "l",
+ "type": "media-block",
+ "direction": "content-media",
+ "anchor": {
+ "url": "1",
+ "text": "Фильм"
+ },
+ "title": "Смотрите фильм с YaC 22
",
+ "description": "Руководители опенсорс‑проектов рассказывают про историю и культуру открытого кода в Яндексе.
",
+ "media": {
+ "youtube": "https://www.youtube.com/watch?v=G7G286S8ntc",
+ "previewImg": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/video-cover.png"
+ },
+ "disableShadow": true
+ },
+ {
+ "type": "content-layout-block",
+ "size": "l",
+ "centered": true,
+ "textContent": {
+ "title": "",
+ "text": "",
+ "additionalInfo": "",
+ "buttons": [
+ {
+ "text": "Сделано на GravityUI",
+ "theme": "monochrome",
+ "img": "https://storage.yandexcloud.net/cloud-www-assets/pages/open-source/open-source-gravity-ui-button.svg",
+ "url": "https://gravity-ui.com/"
+ }
+ ]
+ }
+ }
+ ]
+}
diff --git a/playground/src/pages/pc/example-2/content.json b/playground/src/pages/pc/example-2/content.json
new file mode 100644
index 000000000..568c1b8d2
--- /dev/null
+++ b/playground/src/pages/pc/example-2/content.json
@@ -0,0 +1,402 @@
+{
+ "meta": {
+ "title": "YDB — распределённая SQL база данных с открытым исходным кодом",
+ "description": "YDB — это открытая распределённая SQL база данных, которая сочетает высокую доступность и масштабируемость со строгой согласованностью и ACID транзакциями.",
+ "sharing": {
+ "image": "https://storage.yandexcloud.net/ydb-site-assets/share-ydb-eng.png"
+ }
+ },
+ "blocks": [
+ {
+ "type": "header-block",
+ "title": "YDB",
+ "description": "YDB — это распределённая отказоустойчивая Distributed SQL база данных с открытым исходным кодом, которая сочетает в себе высокую доступность и масштабируемость со строгой согласованностью и транзакциями ACID. Она поддерживает одновременное выполнение транзакционных (OLTP), аналитических (OLAP) и потоковых нагрузок.",
+ "width": "s",
+ "verticalOffset": "m",
+ "imageSize": "m",
+ "background": {
+ "image": {
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/cover.png",
+ "disableCompress": true,
+ "alt": "Фон YDB"
+ },
+ "color": "#e2eaff",
+ "fullWidth": false
+ },
+ "buttons": [
+ {
+ "text": "Быстрый старт",
+ "theme": "accent",
+ "url": "/../docs/ru/quickstart"
+ },
+ {
+ "text": "Документация",
+ "theme": "outlined",
+ "url": "/../docs/ru/"
+ }
+ ]
+ },
+ {
+ "type": "card-layout-block",
+ "title": "Что я могу делать с YDB?",
+ "animated": false,
+ "colSizes": {
+ "all": 12,
+ "sm": 6,
+ "md": 4
+ },
+ "anchor": {
+ "url": "whatcanido",
+ "text": "Что я могу делать с YDB?"
+ },
+ "description": "",
+ "children": [
+ {
+ "type": "background-card",
+ "backgroundColor": "#CCE7FF",
+ "title": "Транзакционные нагрузки (OLTP)",
+ "text": "Вы можете использовать YDB для хранения состояния вашего приложения, независимо от объема данных или частоты их изменения. Обрабатывать петабайты при миллионах транзакций в секунду — не проблема.",
+ "controlPosition": "footer",
+ "buttons": [
+ {
+ "text": "Узнать больше",
+ "theme": "outlined",
+ "url": "/docs/ru/concepts/"
+ }
+ ]
+ },
+ {
+ "type": "background-card",
+ "backgroundColor": "rgba(107,132,153,.12)",
+ "title": "Аналитические нагрузки (OLAP)",
+ "text": "Вы можете создавать аналитические отчёты на основе хранимых в YDB данных с производительностью, сопоставимой со специализированными аналитическими СУБД. При этом никаких компромиссов по согласованности и доступности не потребуется.",
+ "controlPosition": "footer",
+ "buttons": [
+ {
+ "text": "Узнать больше",
+ "theme": "outlined",
+ "url": "/docs/ru/concepts/datamodel/table#column-oriented-tables"
+ }
+ ]
+ },
+ {
+ "type": "background-card",
+ "backgroundColor": "rgba(107,132,153,.12)",
+ "title": "Потоковые нагрузки",
+ "text": "Вы можете использовать функциональность YDB-топиков для надёжной отправки данных между вашими приложениями или отслеживания изменений в таблицах YDB. Можно выбрать как семантику доставки сообщений ровно один раз (exactly once), так и не менее одного раза (at least once).",
+ "controlPosition": "footer",
+ "buttons": [
+ {
+ "text": "Узнать больше",
+ "theme": "outlined",
+ "url": "/docs/ru/concepts/topic"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "extended-features-block",
+ "title": "Почему YDB?",
+ "animated": false,
+ "colSizes": {
+ "all": 12,
+ "sm": 6,
+ "md": 4
+ },
+ "items": [
+ {
+ "title": "Эластичность и масштабируемость",
+ "text": "Добавляйте или удаляйте узлы на лету, чтобы легко масштабировать кластер по мере необходимости. YDB имеет отдельные слои вычисления и хранения, что позволяет независимо добавлять дисковую ёмкость или вычислительные ресурсы в зависимости от того, чего не хватает при текущей нагрузке.",
+ "icon": "https://storage.yandexcloud.net/ydb-site-assets/features-icons/icon_01.svg"
+ },
+ {
+ "title": "Отказоустойчивость",
+ "text": "YDB спроектирована для работы в трёх зонах доступности и обеспечивает работоспособность даже в случае выхода из строя одной из них. Она автоматически восстанавливается после сбоя диска, сервера или датацентра с минимальной задержкой для приложений.",
+ "icon": "https://storage.yandexcloud.net/ydb-site-assets/features-icons/icon_03.svg"
+ },
+ {
+ "title": "Простота в использовании",
+ "text": "Работа с кластером YDB ощущается как работа с одноузловой СУБД с безграничными ресурсами благодаря строгой согласованности, ACID-транзакциям, высокопроизводительным запросам, возможности загрузки больших объёмов данных, а также поддержке знакомого диалекта SQL и JSON API.",
+ "icon": "https://storage.yandexcloud.net/ydb-site-assets/features-icons/icon_02.svg"
+ },
+ {
+ "title": "Универсальность",
+ "text": "Благодаря поддержке различных видов нагрузок в одной системе YDB может заменить несколько систем хранения и обработки данных или всю корпоративную экосистему данных в компании.",
+ "icon": "https://storage.yandexcloud.net/ydb-site-assets/features-icons/icon_06.svg"
+ },
+ {
+ "title": "Открытый исходный код",
+ "text": "[Исходный код YDB](https://github.com/ydb-platform/ydb) опубликован под лицензией Apache 2.0, накладывающей минимум ограничений на использование. Таким образом, нет рисков, связанных с привязкой к конкретному поставщику или провайдеру облачных услуг.",
+ "icon": "https://storage.yandexcloud.net/ydb-site-assets/features-icons/icon_05.svg"
+ },
+ {
+ "title": "Совместимость с любым окружением",
+ "text": "YDB можно развернуть в [Kubernetes](https://github.com/ydb-platform/ydb-kubernetes-operator), в любом облачном окружении или в корпоративных ЦОД. Либо можно использовать YDB как [управляемый сервис в Yandex Cloud](https://cloud.yandex.ru/services/ydb). Также возможны локальные эксперименты на любом компьютере.",
+ "icon": "https://storage.yandexcloud.net/ydb-site-assets/features-icons/icon_04.svg"
+ }
+ ]
+ },
+ {
+ "type": "banner-block",
+ "animated": false,
+ "title": "Оценка производительности PostgreSQL vs Distributed DBMS",
+ "subtitle": "TPC-C является наиболее известным набором тестов производительности для OLTP. Мы подготовили исследование производительности, сравнивающее PostgreSQL, YDB и CockroachDB в отказоустойчивых конфигурациях.",
+ "image": "https://storage.yandexcloud.net/ydb-site-assets/banner-block-pg.png",
+ "color": "#CCE7FF",
+ "button": {
+ "text": "Читать дальше",
+ "theme": "raised",
+ "url": "https://habr.com/ru/companies/ydb/articles/801587/"
+ }
+ },
+ {
+ "type": "card-layout-block",
+ "title": "Кто использует YDB?",
+ "animated": false,
+ "colSizes": {
+ "all": 12,
+ "sm": 6,
+ "md": 4
+ },
+ "anchor": {
+ "url": "whouses",
+ "text": "Кто использует YDB?"
+ },
+ "description": "",
+ "children": [
+ {
+ "type": "layout-item",
+ "media": {
+ "image": {
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/clients/metrika.png",
+ "disableCompress": true
+ }
+ },
+ "content": {
+ "title": "Метрика",
+ "text": "[Метрика](https://metrika.yandex.ru/) - одна из крупнейших в мире платформ мобильной и веб-аналитики. Она полагается на YDB для создания пользовательских сессий на лету.\n\nПереход на YDB позволил Метрике расширить объём хранимых данных и бесконечно наращивать обрабатываемую нагрузку. Теперь одна из баз данных Метрики в YDB содержит более 400 ТБ данных и выдерживает нагрузку более 1 000 000 RPS."
+ },
+ "fullScreen": false,
+ "border": true
+ },
+ {
+ "type": "layout-item",
+ "media": {
+ "image": {
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/clients/yandex-cloud-new.png",
+ "disableCompress": true
+ }
+ },
+ "content": {
+ "title": "Yandex Cloud",
+ "text": "YDB отвечает за слой хранения для сетевых дисков [Yandex Cloud](https://cloud.yandex.ru), используется в качестве СУБД для хранения данных и метаданных облачной инфраструктуры и сервисов платформы, а также в качестве базы данных для облачного Control Plane.\n\nИнфраструктурным и платформенным сервисам Yandex Cloud необходима высокая доступность и масштабируемость, поэтому платформа выбрала YDB в качестве ключевого компонента."
+ },
+ "fullScreen": false,
+ "border": true
+ },
+ {
+ "type": "layout-item",
+ "media": {
+ "image": {
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/clients/praktikum.jpg",
+ "disableCompress": true
+ }
+ },
+ "content": {
+ "title": "Практикум",
+ "text": "[Практикум](https://practicum.yandex.ru/) - это онлайн-ориентированная образовательная платформа. Она использует YDB в качестве гибкого хранилища состояний для своих микросервисов.\n\nНативная поддержка многоарендности в YDB позволяет им избежать создания и управления выделенными базами данных для каждого компонента сервиса."
+ },
+ "fullScreen": false,
+ "border": true
+ },
+ {
+ "type": "layout-item",
+ "media": {
+ "image": {
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/clients/market.png",
+ "disableCompress": true
+ }
+ },
+ "content": {
+ "title": "Яндекс Маркет",
+ "text": "[Яндекс Маркет](https://market.yandex.ru) - один из крупнейших сервисов электронной коммерции в СНГ. Многие ключевые функции сервиса, такие как корзина, скидки, и оформление заказа, используют YDB для хранения своего состояния.\n\nВыбор YDB в качестве базы данных позволил Яндекс Маркету выдержать стократное увеличение нагрузки на корзину при соблюдении строгих гарантий времени отклика."
+ },
+ "fullScreen": false,
+ "border": true
+ },
+ {
+ "type": "layout-item",
+ "media": {
+ "image": {
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/clients/jaeger.png",
+ "disableCompress": true
+ }
+ },
+ "content": {
+ "title": "Auto.ru",
+ "text": "[Auto.ru](https://auto.ru) снизила потребление CPU для трассировочной базы данных [Jaeger](https://www.jaegertracing.io/) в три раза после перехода на YDB, что позволило записывать 500 000 трассировок в секунду без семплирования.\n\nУспешная реализация YDB в качестве хранилища трассировок доказала применимость и ключевые свойства, такие как масштабируемость, отказоустойчивость и строгая согласованность. В результате Auto.ru выбрала YDB в качестве реляционной базы данных для некоторых своих микросервисов."
+ },
+ "fullScreen": false,
+ "border": true
+ },
+ {
+ "type": "layout-item",
+ "media": {
+ "image": {
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/clients/alice-new-speakers.png",
+ "disableCompress": "true\""
+ }
+ },
+ "content": {
+ "title": "Алиса",
+ "text": "[Алиса](https://yandex.ru/alice) - это голосовой помощник и экосистема умного дома. После перехода на YDB команда Алисы решила проблемы синхронизации между дата‑центрами, простоев при переключении основных серверов, снизила нагрузку на команду DevOps, увеличила объём хранимых данных до сотен терабайт и нагрузку до сотен тысяч запросов в секунду.\n\nПереход на YDB позволил отказаться от ручного шардинга данных и добиться строгой согласованности в кросс-датацентровом кластере. Сейчас команда Алисы использует YDB как реляционную базу данных и как базу для хранения логов и трейсов."
+ },
+ "fullScreen": false,
+ "border": true
+ }
+ ]
+ },
+ {
+ "type": "slider-block",
+ "animated": false,
+ "anchor": {
+ "url": "scenarios",
+ "text": "scenarios"
+ },
+ "title": {
+ "text": "В каких типовых сценариях стоит использовать YDB?",
+ "textSize": "m"
+ },
+ "children": [
+ {
+ "type": "basic-card",
+ "title": "Работа с внезапным ростом нагрузки",
+ "text": "Эластичность YDB позволяет быстро изменять количество ресурсов, выделенных базе данных, чтобы обеспечить необходимую пропускную способность в соответствии с нагрузкой. Вы можете легко увеличить или уменьшить количество вычислительных ресурсов в зависимости от предстоящих нагрузок, например, на «Чёрную пятницу» или для маркетинговых кампаний."
+ },
+ {
+ "type": "basic-card",
+ "title": "Хранение Observability данных",
+ "text": "[Колоночные таблицы YDB](/docs/ru/concepts/column-table) отлично подходят для хранения логов и метрик с простым доступом к ним через SQL интерфейс. Также, низкое потребление вычислительных ресурсов и масштабируемость YDB делают запись [Jaeger трейсов](https://www.jaegertracing.io/) выгодной по себестоимости и лёгкой в использовании."
+ },
+ {
+ "type": "basic-card",
+ "title": "Документоориентированная СУБД",
+ "text": "Не смотря на строгую систему типов данных YDB, поддержка [типа данных JSON и связанных с ним функций](/docs/ru/yql/reference/builtins/json), расширяет возможности YDB в роли системы хранения неструктурированных документов."
+ },
+ {
+ "type": "basic-card",
+ "title": "Кеш с SQL-интерфейсом",
+ "text": "Быстрый отклик и масштабируемость пропускной способности позволяют использовать YDB одновременно в качестве онлайн-базы данных и предварительно посчитанного кеша. Возможности SQL значительно повышают удобство использования и позволяют проводить оперативную аналитику данных в кеше. Например, сайты туроператоров и агрегаторов путешествий могут использовать базу данных для кеширования результатов поиска авиабилетов или туров, а также для пересчёта цен и проверки сезонной доступности."
+ },
+ {
+ "type": "basic-card",
+ "title": "Централизованная система учёта запасов",
+ "text": "YDB обеспечивает строгую целостность транзакций. Это позволяет предоставлять консистентные данные о запасах на всех складах и торговых объектах, и делает YDB подходящим решением для электронной коммерции, приложений складской или транспортной логистики."
+ },
+ {
+ "type": "basic-card",
+ "title": "Система хранения данных для Internet of Things (IoT) экосистемы",
+ "text": "Поддержка автоматического шардирования YDB позволяет обрабатывать потоки данных от большого количества устройств — профиль нагрузки, который встречается в проектах интернета вещей."
+ }
+ ]
+ },
+ {
+ "type": "media-block",
+ "animated": false,
+ "direction": "content-media",
+ "title": "Можете вкратце объяснить, что такое YDB, в видеоформате?",
+ "description": "Конечно, смотрите →",
+ "anchor": {
+ "url": "video",
+ "text": "Video"
+ },
+ "largeMedia": true,
+ "media": {
+ "youtube": "https://youtu.be/QVmhR__wAJg"
+ }
+ },
+ {
+ "type": "banner-block",
+ "animated": false,
+ "title": "Учебный курс по YDB от участника сообщества пользователей",
+ "subtitle": "_Сторонняя разработка - Contribution_\n\nНачальный [учебный курс по YDB](https://stepik.org/course/186264/promo), разработанный одним из наших пользователей - Владом Бурмистровым, позволяет ознакомиться с ключевыми свойствами, возможностями и архитектурой YDB, а также научиться основным приемам по работе с YDB.",
+ "color": "#CCE7FF",
+ "image": "https://storage.yandexcloud.net/ydb-site-assets/careers/img_4.png",
+ "button": {
+ "text": "Описание и программа курса",
+ "theme": "raised",
+ "url": "https://stepik.org/course/186264/promo"
+ }
+ },
+ {
+ "type": "tabs-block",
+ "title": {
+ "text": "Как начать?"
+ },
+ "anchor": {
+ "url": "howtostart",
+ "text": "Как начать?"
+ },
+ "items": [
+ {
+ "tabName": "Docker",
+ "title": "Docker",
+ "text": " Создайте рабочий каталог и запустите локальный контейнер YDB из этого каталога:\n```bash mkdir ydb-local && cd ydb-local \ndocker run -d --rm --name ydb-local -h localhost \\\n--platform linux/amd64 \\\n-p 2135:2135 -p 2136:2136 -p 8765:8765 \\\n-v $(pwd)/ydb_certs:/ydb_certs -v $(pwd)/ydb_data:/ydb_data \\\n-e GRPC_TLS_PORT=2135 -e GRPC_PORT=2136 -e MON_PORT=8765 \\\nydbplatform/local-ydb:latest\n```\nПерейдите в раздел [«Быстрый старт»](/docs/ru/quickstart) в документации YDB, чтобы получить дополнительную информацию. "
+ },
+ {
+ "tabName": "Minikube",
+ "title": "Minikube",
+ "text": " Установите Kubernetes CLI [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl) и менеджер пакетов [Helm 3](https://helm.sh/docs/intro/install/).\nУстановите и запустите [Minikube](https://kubernetes.io/ru/docs/tasks/tools/install-minikube/).\nСклонируйте репозиторий с [YDB Kubernetes Operator](https://github.com/ydb-platform/ydb-kubernetes-operator).\n```\ngit clone https://github.com/ydb-platform/ydb-kubernetes-operator && cd ydb-kubernetes-operator\n```\nУстановите контроллер YDB на кластер.\n```\nhelm upgrade --install ydb-operator deploy/ydb-operator --set metrics.enabled=false\n```\nПримените манифест для создания кластера YDB.\n```\nkubectl apply -f samples/minikube/storage.yaml\n```\nДождитесь, пока `kubectl get storages.ydb.tech` не станет `Ready`.\nПримените манифест для создания базы данных.\n```\nkubectl apply -f samples/minikube/database.yaml\n```\nДождитесь, пока `kubectl get databases.ydb.tech` не станет `Ready`.\n\nПосле обработки манифеста будет создан объект StatefulSet, описывающий набор динамических узлов. Созданная база данных будет доступна изнутри кластера Kubernetes по DNS имени `database-minikube-sample` на порту 2135.\n\nПерейдите в раздел [«Быстрый старт»](/docs/ru/quickstart) в документации YDB, чтобы получить дополнительную информацию. "
+ },
+ {
+ "tabName": "Установка вручную",
+ "title": "Установка вручную (только для Linux x86_64)",
+ "text": " Создайте рабочий каталог, запустите скрипт установки из него, а затем запустите одноузловую базу данных YDB с включенным режимом работы в оперативной памяти:\n``` mkdir ydb-local && cd ydb-local\ncurl https://install.ydb.tech | bash\n./start.sh ram\n```\nПерейдите в раздел [«Быстрый старт»](/docs/ru/quickstart) в документации YDB, чтобы получить дополнительную информацию. "
+ }
+ ]
+ },
+ {
+ "type": "icons-block",
+ "size": "s",
+ "title": "Как оставаться на связи?",
+ "items": [
+ {
+ "url": "https://github.com/ydb-platform/ydb",
+ "text": "GitHub",
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/community/git.svg"
+ },
+ {
+ "url": "https://t.me/ydb_ru",
+ "text": "Telegram",
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/community/telegram.svg"
+ },
+ {
+ "url": "https://habr.com/ru/companies/ydb/articles/",
+ "text": "Habr",
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/community/habr.svg"
+ },
+ {
+ "url": "https://blog.ydb.tech",
+ "text": "Medium",
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/community/medium.svg"
+ },
+ {
+ "url": "https://twitter.com/YDBPlatform",
+ "text": "Twitter",
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/community/twitter.svg"
+ },
+ {
+ "url": "https://www.linkedin.com/company/ydb-platform/",
+ "text": "LinkedIn",
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/community/linkedin.svg"
+ },
+ {
+ "url": "https://www.youtube.com/c/YDBPlatform",
+ "text": "YouTube",
+ "src": "https://storage.yandexcloud.net/ydb-site-assets/community/youtube.svg"
+ }
+ ]
+ }
+ ]
+}
diff --git a/playground/src/pages/pc/example-2/navigation.json b/playground/src/pages/pc/example-2/navigation.json
new file mode 100644
index 000000000..8df0e9ccc
--- /dev/null
+++ b/playground/src/pages/pc/example-2/navigation.json
@@ -0,0 +1,48 @@
+{
+ "logo": {
+ "text": "",
+ "icon": "https://storage.yandexcloud.net/ydb-site-assets/ydb_icon.svg"
+ },
+ "header": {
+ "leftItems": [
+ {
+ "type": "link",
+ "text": "Документация",
+ "url": "/../docs/ru",
+ "target": "_self"
+ },
+ {
+ "type": "link",
+ "text": "Команда",
+ "url": "/careers/"
+ },
+ {
+ "type": "link",
+ "text": "Студентам",
+ "url": "/students/"
+ },
+ {
+ "type": "link",
+ "text": "Поддержка",
+ "url": "/support/"
+ },
+ {
+ "type": "link",
+ "text": "Блог",
+ "url": "/blog/"
+ }
+ ],
+ "rightItems": [
+ {
+ "type": "github-button",
+ "text": "Star",
+ "label": "Star ydb-platform/ydb on GitHub",
+ "url": "https://github.com/ydb-platform/ydb"
+ }
+ ]
+ },
+ "meta": {
+ "title": "",
+ "description": ""
+ }
+}
diff --git a/playground/src/pages/pc/example-2/styles.scss b/playground/src/pages/pc/example-2/styles.scss
new file mode 100644
index 000000000..158d94216
--- /dev/null
+++ b/playground/src/pages/pc/example-2/styles.scss
@@ -0,0 +1,116 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap');
+
+.g-root_theme_light {
+ --g-border-radius-xs: 0px;
+ --g-border-radius-s: 0px;
+ --g-border-radius-m: 0px;
+ --g-border-radius-l: 0px;
+ --g-border-radius-xl: 0px;
+
+ --g-font-family-sans: 'Inter', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
+ --g-font-family-monospace: 'Roboto Mono', 'Menlo', 'Monaco', 'Consolas', 'Ubuntu Mono',
+ 'Liberation Mono', 'DejaVu Sans Mono', 'Courier New', 'Courier', monospace;
+
+ --g-color-private-brand-50: rgba(203, 255, 92, 0.1);
+ --g-color-private-brand-100: rgba(203, 255, 92, 0.15);
+ --g-color-private-brand-150: rgba(203, 255, 92, 0.2);
+ --g-color-private-brand-200: rgba(203, 255, 92, 0.3);
+ --g-color-private-brand-250: rgba(203, 255, 92, 0.4);
+ --g-color-private-brand-300: rgba(203, 255, 92, 0.5);
+ --g-color-private-brand-350: rgba(203, 255, 92, 0.6);
+ --g-color-private-brand-400: rgba(203, 255, 92, 0.7);
+ --g-color-private-brand-450: rgba(203, 255, 92, 0.8);
+ --g-color-private-brand-500: rgba(203, 255, 92, 0.9);
+ --g-color-private-brand-550-solid: rgb(203, 255, 92);
+ --g-color-private-brand-1000-solid: rgb(59, 63, 43);
+ --g-color-private-brand-950-solid: rgb(68, 74, 46);
+ --g-color-private-brand-900-solid: rgb(85, 97, 51);
+ --g-color-private-brand-850-solid: rgb(102, 119, 57);
+ --g-color-private-brand-800-solid: rgb(119, 142, 63);
+ --g-color-private-brand-750-solid: rgb(135, 165, 69);
+ --g-color-private-brand-700-solid: rgb(152, 187, 75);
+ --g-color-private-brand-650-solid: rgb(169, 210, 80);
+ --g-color-private-brand-600-solid: rgb(186, 232, 86);
+ --g-color-private-brand-500-solid: rgb(208, 255, 108);
+ --g-color-private-brand-450-solid: rgb(213, 255, 125);
+ --g-color-private-brand-400-solid: rgb(219, 255, 141);
+ --g-color-private-brand-350-solid: rgb(224, 255, 157);
+ --g-color-private-brand-300-solid: rgb(229, 255, 174);
+ --g-color-private-brand-250-solid: rgb(234, 255, 190);
+ --g-color-private-brand-200-solid: rgb(239, 255, 206);
+ --g-color-private-brand-150-solid: rgb(245, 255, 222);
+ --g-color-private-brand-100-solid: rgb(247, 255, 231);
+ --g-color-private-brand-50-solid: rgb(250, 255, 239);
+
+ --g-color-base-brand: rgb(203, 255, 92);
+ --g-color-base-background: rgb(255, 255, 255);
+ --g-color-base-brand-hover: var(--g-color-private-brand-600-solid);
+ --g-color-base-selection: var(--g-color-private-brand-200);
+ --g-color-base-selection-hover: var(--g-color-private-brand-300);
+ --g-color-line-brand: var(--g-color-private-brand-600-solid);
+ --g-color-text-brand: var(--g-color-private-brand-700-solid);
+ --g-color-text-brand-heavy: var(--g-color-private-brand-700-solid);
+ --g-color-text-brand-contrast: rgba(0, 0, 0, 0.85);
+ --g-color-text-link: var(--g-color-private-brand-600-solid);
+ --g-color-text-link-hover: var(--g-color-private-brand-800-solid);
+ --g-color-text-link-visited: var(--g-color-private-purple-550-solid);
+ --g-color-text-link-visited-hover: var(--g-color-private-purple-800-solid);
+}
+
+.g-root_theme_dark {
+ --g-border-radius-xs: 0px;
+ --g-border-radius-s: 0px;
+ --g-border-radius-m: 0px;
+ --g-border-radius-l: 0px;
+ --g-border-radius-xl: 0px;
+
+ --g-font-family-sans: 'Inter', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
+ --g-font-family-monospace: 'Roboto Mono', 'Menlo', 'Monaco', 'Consolas', 'Ubuntu Mono',
+ 'Liberation Mono', 'DejaVu Sans Mono', 'Courier New', 'Courier', monospace;
+
+ --g-color-private-brand-50: rgba(203, 255, 92, 0.1);
+ --g-color-private-brand-100: rgba(203, 255, 92, 0.15);
+ --g-color-private-brand-150: rgba(203, 255, 92, 0.2);
+ --g-color-private-brand-200: rgba(203, 255, 92, 0.3);
+ --g-color-private-brand-250: rgba(203, 255, 92, 0.4);
+ --g-color-private-brand-300: rgba(203, 255, 92, 0.5);
+ --g-color-private-brand-350: rgba(203, 255, 92, 0.6);
+ --g-color-private-brand-400: rgba(203, 255, 92, 0.7);
+ --g-color-private-brand-450: rgba(203, 255, 92, 0.8);
+ --g-color-private-brand-500: rgba(203, 255, 92, 0.9);
+ --g-color-private-brand-550-solid: rgb(203, 255, 92);
+ --g-color-private-brand-1000-solid: rgb(247, 255, 231);
+ --g-color-private-brand-950-solid: rgb(245, 255, 222);
+ --g-color-private-brand-900-solid: rgb(239, 255, 206);
+ --g-color-private-brand-850-solid: rgb(234, 255, 190);
+ --g-color-private-brand-800-solid: rgb(229, 255, 174);
+ --g-color-private-brand-750-solid: rgb(224, 255, 157);
+ --g-color-private-brand-700-solid: rgb(219, 255, 141);
+ --g-color-private-brand-650-solid: rgb(213, 255, 125);
+ --g-color-private-brand-600-solid: rgb(208, 255, 108);
+ --g-color-private-brand-500-solid: rgb(186, 232, 86);
+ --g-color-private-brand-450-solid: rgb(169, 210, 80);
+ --g-color-private-brand-400-solid: rgb(152, 187, 75);
+ --g-color-private-brand-350-solid: rgb(135, 165, 69);
+ --g-color-private-brand-300-solid: rgb(119, 142, 63);
+ --g-color-private-brand-250-solid: rgb(102, 119, 57);
+ --g-color-private-brand-200-solid: rgb(85, 97, 51);
+ --g-color-private-brand-150-solid: rgb(68, 74, 46);
+ --g-color-private-brand-100-solid: rgb(59, 63, 43);
+ --g-color-private-brand-50-solid: rgb(51, 52, 40);
+
+ --g-color-base-brand: rgb(203, 255, 92);
+ --g-color-base-background: rgb(34, 29, 34);
+ --g-color-base-brand-hover: var(--g-color-private-brand-650-solid);
+ --g-color-base-selection: var(--g-color-private-brand-150);
+ --g-color-base-selection-hover: var(--g-color-private-brand-200);
+ --g-color-line-brand: var(--g-color-private-brand-600-solid);
+ --g-color-text-brand: var(--g-color-private-brand-600-solid);
+ --g-color-text-brand-heavy: var(--g-color-private-brand-700-solid);
+ --g-color-text-brand-contrast: rgba(0, 0, 0, 0.9);
+ --g-color-text-link: var(--g-color-private-brand-550-solid);
+ --g-color-text-link-hover: var(--g-color-private-brand-700-solid);
+ --g-color-text-link-visited: var(--g-color-private-purple-700-solid);
+ --g-color-text-link-visited-hover: var(--g-color-private-purple-850-solid);
+}
diff --git a/playground/src/pages/pc/pc.tsx b/playground/src/pages/pc/pc.tsx
new file mode 100644
index 000000000..2d46b8b2b
--- /dev/null
+++ b/playground/src/pages/pc/pc.tsx
@@ -0,0 +1,43 @@
+import * as React from 'react';
+import {NavigationData, PageConstructor, PageConstructorProvider} from '../../../../src';
+
+// Example 1
+import contentExample1 from './example-1/content.json';
+
+// Example 2
+import contentExample2 from './example-2/content.json';
+import navigationExample2 from './example-2/navigation.json';
+
+interface PCPageProps {
+ id?: string | null;
+}
+
+export default function PCPage({id}: PCPageProps) {
+ const pageId = id || '1';
+
+ const page = React.useMemo(() => {
+ switch (Number(pageId)) {
+ case 2:
+ import('./example-2/styles.scss');
+ return {
+ content: contentExample2,
+ navigation: navigationExample2,
+ };
+ default:
+ case 1:
+ return {
+ content: contentExample1,
+ navigation: undefined,
+ };
+ }
+ }, [pageId]);
+
+ return (
+
+
+
+ );
+}
diff --git a/playground/src/router.tsx b/playground/src/router.tsx
new file mode 100644
index 000000000..d79a7bd20
--- /dev/null
+++ b/playground/src/router.tsx
@@ -0,0 +1,21 @@
+import {useSearchParams} from 'react-router';
+
+import EditorPage from './pages/editor/editor';
+import PCPage from './pages/pc/pc';
+import FormPage from './pages/form/form';
+
+export default function Router() {
+ const [searchParams] = useSearchParams();
+ const page = searchParams.get('page');
+ const id = searchParams.get('id');
+
+ // Route based on query parameters
+ switch (page) {
+ case 'form':
+ return ;
+ case 'pc':
+ return ;
+ default:
+ return ;
+ }
+}
diff --git a/playground/src/styles/globals.scss b/playground/src/styles/globals.scss
new file mode 100644
index 000000000..5c7535e5a
--- /dev/null
+++ b/playground/src/styles/globals.scss
@@ -0,0 +1,6 @@
+html,
+body,
+#root {
+ min-width: 320px;
+ margin: 0;
+}
diff --git a/playground/src/vite-env.d.ts b/playground/src/vite-env.d.ts
new file mode 100644
index 000000000..b1f45c786
--- /dev/null
+++ b/playground/src/vite-env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/playground/tsconfig.json b/playground/tsconfig.json
new file mode 100644
index 000000000..8261a7284
--- /dev/null
+++ b/playground/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "extends": "@gravity-ui/tsconfig/tsconfig",
+ "compilerOptions": {
+ "target": "es2022",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "types": ["vite-plugin-svgr/client"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "allowSyntheticDefaultImports": true,
+ "paths": {
+ "@/*": ["./src/*"],
+ "*": ["../src/internal-typings/*"]
+ }
+ },
+ "include": ["**/*.ts", "**/*.tsx", "vite.config.mts", "../src/internal-typings/*"],
+ "exclude": ["node_modules"]
+}
diff --git a/playground/vite.config.mts b/playground/vite.config.mts
new file mode 100644
index 000000000..bc93fe0aa
--- /dev/null
+++ b/playground/vite.config.mts
@@ -0,0 +1,26 @@
+import {defineConfig} from 'vite';
+import react from '@vitejs/plugin-react';
+import svgr from 'vite-plugin-svgr';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [svgr(), react()],
+ optimizeDeps: {
+ //workaround for the problem https://github.com/vitejs/vite/issues/7719
+ extensions: ['.css'],
+ esbuildOptions: {
+ plugins: [
+ (await import('esbuild-sass-plugin')).sassPlugin({
+ type: 'style',
+ }),
+ ],
+ },
+ },
+ css: {
+ preprocessorOptions: {
+ scss: {
+ silenceDeprecations: ['import', 'mixed-decls', 'global-builtin'],
+ },
+ },
+ },
+});
diff --git a/src/blocks/Banner/Banner.tsx b/src/blocks/Banner/Banner.tsx
index 89a7906fc..53b3a1316 100644
--- a/src/blocks/Banner/Banner.tsx
+++ b/src/blocks/Banner/Banner.tsx
@@ -1,4 +1,5 @@
import AnimateBlock from '../../components/AnimateBlock/AnimateBlock';
+import {Grid} from '../../grid';
import {BannerBlockProps} from '../../models';
import {BannerCard} from '../../sub-blocks';
import {block} from '../../utils';
@@ -12,7 +13,9 @@ export const BannerBlock = (props: BannerBlockProps) => {
return (
-
+
+
+
);
};
diff --git a/src/blocks/Banner/index.tsx b/src/blocks/Banner/index.tsx
new file mode 100644
index 000000000..2b92af495
--- /dev/null
+++ b/src/blocks/Banner/index.tsx
@@ -0,0 +1,26 @@
+import {JSONSchemaType} from 'ajv';
+
+import {BlockData} from '../../constructor-items';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import BannerBlock from './Banner';
+import {BannerCardProps} from './schema';
+
+const BannerBlockConfig: BlockData = {
+ component: BannerBlock,
+ schema: {
+ name: 'Banner Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(BannerCardProps as unknown as JSONSchemaType<{}>),
+ default: {
+ color: 'rgba(54, 151, 241, 0.4)',
+ title: 'Banner Block',
+ subtitle: 'Some sort of description.',
+ button: {
+ text: 'Read more',
+ },
+ },
+ },
+};
+
+export default BannerBlockConfig;
diff --git a/src/blocks/CardLayout/CardLayout.scss b/src/blocks/CardLayout/CardLayout.scss
index c4a484533..9c178834c 100644
--- a/src/blocks/CardLayout/CardLayout.scss
+++ b/src/blocks/CardLayout/CardLayout.scss
@@ -26,6 +26,7 @@ $largeBorderRadius: 32px;
width: 100%;
height: 100%;
border-radius: $largeBorderRadius;
+ pointer-events: none;
img {
object-fit: cover;
diff --git a/src/blocks/CardLayout/CardLayout.tsx b/src/blocks/CardLayout/CardLayout.tsx
index bc8fa3856..ba8357594 100644
--- a/src/blocks/CardLayout/CardLayout.tsx
+++ b/src/blocks/CardLayout/CardLayout.tsx
@@ -2,8 +2,9 @@ import * as React from 'react';
import isEmpty from 'lodash/isEmpty';
import {AnimateBlock, BackgroundImage, Title} from '../../components';
+import ItemWrap from '../../components/editor/ItemWrap/ItemWrap';
import {useTheme} from '../../context/theme';
-import {Col, GridColumnSizesType, Row} from '../../grid';
+import {Col, Grid, GridColumnSizesType, Row} from '../../grid';
import {CardLayoutBlockProps as CardLayoutBlockParams, ClassNameProps} from '../../models';
import {block, getThemedValue} from '../../utils';
@@ -35,23 +36,26 @@ const CardLayout: React.FC = ({
const {border, ...backgroundImageProps} = getThemedValue(background || {}, theme);
return (
- {(title || description) && (
-
- )}
-
-
-
- {React.Children.map(children, (child, index) => (
-
- {child}
-
- ))}
-
-
+
+ {(title || description) && (
+
+ )}
+
+
+
+
+ {React.Children.map(children, (child, index) => (
+
+ {child}
+
+ ))}
+
+
+
);
};
diff --git a/src/blocks/CardLayout/index.ts b/src/blocks/CardLayout/index.ts
new file mode 100644
index 000000000..dfe358c01
--- /dev/null
+++ b/src/blocks/CardLayout/index.ts
@@ -0,0 +1,191 @@
+import {JSONSchemaType} from 'ajv';
+
+import {ConfigInput} from '../../form-generator';
+import {BlockData} from '../../constructor-items';
+import {sliderSizesArray, textSize} from '../../schema/validators/common';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import CardLayout from './CardLayout';
+import {CardLayoutProps} from './schema';
+
+const textSizeEnum = textSize.map((size) => ({value: size, content: size}));
+
+export const blockConfig: ConfigInput[] = [
+ {
+ type: 'oneOf',
+ name: 'title',
+ title: 'Title Object',
+ options: [
+ {
+ title: 'Simple',
+ value: 'simple',
+ properties: [
+ {
+ type: 'text',
+ name: '',
+ title: 'Title',
+ },
+ ],
+ },
+ {
+ title: 'Complex',
+ value: 'complex',
+ properties: [
+ {
+ type: 'text',
+ name: 'text',
+ title: 'Title',
+ },
+ {
+ type: 'select',
+ name: 'textSize',
+ title: 'Text Size',
+ enum: textSizeEnum,
+ view: 'select',
+ mode: 'single',
+ },
+ {
+ type: 'text',
+ name: 'url',
+ title: 'Url',
+ },
+ {
+ type: 'text',
+ name: 'urlTitle',
+ title: 'Url',
+ },
+ {
+ type: 'boolean',
+ name: 'resetMargin',
+ title: 'Reset Margin',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'textarea',
+ name: 'description',
+ title: 'Description',
+ },
+ {
+ type: 'boolean',
+ name: 'dots',
+ title: 'With dots',
+ },
+ {
+ type: 'boolean',
+ name: 'arrows',
+ title: 'With Arrows',
+ },
+ {
+ type: 'boolean',
+ name: 'randomOrder',
+ title: 'Random Order',
+ },
+ {
+ type: 'number',
+ name: 'autoplay',
+ title: 'Autoplay',
+ },
+ {
+ type: 'object',
+ name: 'disclaimer',
+ title: 'Disclaimer',
+ properties: [
+ {
+ type: 'text',
+ name: 'text',
+ title: 'Text',
+ },
+ {
+ type: 'select',
+ name: 'size',
+ title: 'Size',
+ enum: textSizeEnum,
+ view: 'select',
+ mode: 'single',
+ },
+ ],
+ },
+ {
+ type: 'oneOf',
+ name: 'slidesToShow',
+ title: 'Slides to Show',
+ options: [
+ {
+ title: 'Simple',
+ value: 'simple',
+ properties: [
+ {
+ type: 'number',
+ name: '',
+ title: 'Slides',
+ },
+ ],
+ },
+ {
+ title: 'Complex',
+ value: 'complex',
+ properties: sliderSizesArray.reduce((acc, size) => {
+ acc.push({type: 'number', name: size, title: size});
+ return acc;
+ }, [] as Array),
+ },
+ ],
+ },
+ {
+ type: 'array',
+ name: 'array',
+ title: 'Array Properties',
+ buttonText: 'Add new',
+ arrayType: 'object',
+ properties: [
+ {
+ type: 'text',
+ name: 'text',
+ title: 'Property',
+ },
+ {
+ type: 'boolean',
+ name: 'boolean',
+ title: 'Property',
+ },
+ ],
+ },
+];
+
+const CardLayoutBlockConfig: BlockData = {
+ component: CardLayout,
+ schema: {
+ name: 'Card Layout Block',
+ group: '@gravity-ui/page-constructor/Card Containers',
+ inputs: generateFromAJV(CardLayoutProps as unknown as JSONSchemaType<{}>),
+ default: {
+ type: 'card-layout-block',
+ children: [
+ {
+ type: 'background-card',
+ title: 'Tell a story and build a narrative',
+ text: 'We are all storytellers. Stories are a powerful way to communicate ideas and share information. The right story can lead to a better understanding of a situation, make us laugh, or even inspire us to do something in the future.',
+ },
+ {
+ type: 'background-card',
+ title: 'Tell a story and build a narrative',
+ text: 'We are all storytellers. Stories are a powerful way to communicate ideas and share information. The right story can lead to a better understanding of a situation, make us laugh, or even inspire us to do something in the future.',
+ },
+ {
+ type: 'background-card',
+ title: 'Tell a story and build a narrative',
+ text: 'We are all storytellers. Stories are a powerful way to communicate ideas and share information. The right story can lead to a better understanding of a situation, make us laugh, or even inspire us to do something in the future.',
+ },
+ ],
+ title: 'Card Layout Block',
+ description:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ previewImg: 'https://storage.cloud-preprod.yandex.net/qradle-test/card-layout-block.svg',
+ },
+};
+
+export default CardLayoutBlockConfig;
diff --git a/src/blocks/Companies/Companies.tsx b/src/blocks/Companies/Companies.tsx
index d16d78a05..c200e4528 100644
--- a/src/blocks/Companies/Companies.tsx
+++ b/src/blocks/Companies/Companies.tsx
@@ -1,6 +1,7 @@
import {Image, Title} from '../../components';
import AnimateBlock from '../../components/AnimateBlock/AnimateBlock';
import {useTheme} from '../../context/theme';
+import {Grid} from '../../grid';
import {CompaniesBlockProps} from '../../models';
import {block, getThemedValue} from '../../utils';
@@ -14,12 +15,18 @@ export const CompaniesBlock = ({title, description, images, animated}: Companies
return (
-
-
-
+
);
};
diff --git a/src/blocks/Companies/index.ts b/src/blocks/Companies/index.ts
new file mode 100644
index 000000000..e1319489b
--- /dev/null
+++ b/src/blocks/Companies/index.ts
@@ -0,0 +1,24 @@
+import {JSONSchemaType} from 'ajv';
+
+import {BlockData} from '../../constructor-items';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import CompaniesBlock from './Companies';
+import {CompaniesBlock as CompaniesBlockSchema} from './schema';
+
+const CompaniesBlockConfig: BlockData = {
+ component: CompaniesBlock,
+ schema: {
+ name: 'Companies Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(
+ CompaniesBlockSchema['companies-block'] as unknown as JSONSchemaType<{}>,
+ ),
+ default: {
+ title: 'Companies Block',
+ description: 'Here is the list',
+ },
+ },
+};
+
+export default CompaniesBlockConfig;
diff --git a/src/blocks/ContentLayout/ContentLayout.tsx b/src/blocks/ContentLayout/ContentLayout.tsx
index 7706d229a..ba26121d9 100644
--- a/src/blocks/ContentLayout/ContentLayout.tsx
+++ b/src/blocks/ContentLayout/ContentLayout.tsx
@@ -3,7 +3,7 @@ import * as React from 'react';
import {BackgroundImage, FileLink} from '../../components';
import {MobileContext} from '../../context/mobileContext';
import {useTheme} from '../../context/theme';
-import {Col} from '../../grid';
+import {Col, Grid} from '../../grid';
import {ContentLayoutBlockProps, ContentSize, ContentTextSize} from '../../models';
import {Content} from '../../sub-blocks';
import {block, getThemedValue} from '../../utils';
@@ -51,39 +51,41 @@ export const ContentLayoutBlock = (props: ContentLayoutBlockProps) => {
const themedBackground = getThemedValue(background, globalTheme);
return (
-
-
- {fileContent && (
-
- {fileContent.map((file) => (
-
+
+
+ {fileContent && (
+
+ {fileContent.map((file) => (
+
+ ))}
+
+ )}
+ {background && (
+
+
- ))}
-
- )}
- {background && (
-
-
-
- )}
-
+
+ )}
+
+
);
};
export default ContentLayoutBlock;
diff --git a/src/blocks/ContentLayout/index.ts b/src/blocks/ContentLayout/index.ts
new file mode 100644
index 000000000..7c17d6953
--- /dev/null
+++ b/src/blocks/ContentLayout/index.ts
@@ -0,0 +1,85 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import ContentLayoutBlock from './ContentLayout';
+import {ContentLayoutBlock as ContentLayoutBlockSchema} from './schema';
+
+const ContentLayoutBlockConfig = {
+ component: ContentLayoutBlock,
+ schema: {
+ name: 'Content Layout Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(
+ ContentLayoutBlockSchema['content-layout-block'] as unknown as JSONSchemaType<{}>,
+ ),
+ default: {
+ textContent: {
+ title: 'Lorem ipsum dolor sit amet',
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ },
+ additionalInfo:
+ 'Duis aute irure dolor in reprehenderit n voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
+ buttons: [
+ {
+ text: 'Button',
+ theme: 'action',
+ url: 'https://example.com',
+ },
+ {
+ text: 'Button',
+ theme: 'outlined',
+ url: 'https://example.com',
+ },
+ ],
+ links: [
+ {
+ url: 'https://example.com',
+ text: 'Link',
+ theme: 'normal',
+ arrow: true,
+ },
+ ],
+ fileContent: [
+ {
+ href: 'https://example.xls',
+ text: 'File xls',
+ },
+ {
+ href: 'https://example.fig',
+ text: 'File PNG, JPG, and SVG format',
+ },
+ {
+ href: 'https://example.pdf',
+ text: 'Pdf file',
+ },
+ {
+ href: 'https://example.zip',
+ text: 'Archive file',
+ },
+ {
+ href: 'https://example.doc',
+ text: 'Microsoft Word document',
+ },
+ {
+ href: 'https://example.ppt',
+ text: 'PPT file',
+ },
+ ],
+ list: [
+ {
+ title: 'Lorem ipsum',
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ },
+ {
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ },
+ {
+ title: 'Lorem ipsum ipsum',
+ },
+ ],
+ },
+ },
+};
+
+export default ContentLayoutBlockConfig;
diff --git a/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx b/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx
index 0d034fa00..094d8b2de 100644
--- a/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx
+++ b/src/blocks/ExtendedFeatures/ExtendedFeatures.tsx
@@ -4,7 +4,7 @@ import {AnimateBlock, HTML, Title} from '../../components/';
import Image from '../../components/Image/Image';
import {getMediaImage} from '../../components/Media/Image/utils';
import {useTheme} from '../../context/theme';
-import {Col, Row} from '../../grid';
+import {Col, Grid, Row} from '../../grid';
import {ExtendedFeaturesProps} from '../../models';
import {Content} from '../../sub-blocks';
import {block, getThemedValue} from '../../utils';
@@ -31,69 +31,75 @@ export const ExtendedFeaturesBlock = ({
return (
-
-
-
- {items.map(
- ({
- title: itemTitle,
- text,
- list,
- link,
- links,
- label,
- icon,
- buttons,
- additionalInfo,
- }) => {
- const itemLinks = links || [];
+
+
+
+
+ {items &&
+ items.map(
+ (
+ {
+ title: itemTitle,
+ text,
+ list,
+ link,
+ links,
+ label,
+ icon,
+ buttons,
+ additionalInfo,
+ },
+ index,
+ ) => {
+ const itemLinks = links || [];
- const iconThemed = icon && getThemedValue(icon, theme);
- const iconData = iconThemed && getMediaImage(iconThemed);
+ const iconThemed = icon && getThemedValue(icon, theme);
+ const iconData = iconThemed && getMediaImage(iconThemed);
- if (link) {
- itemLinks.push(link);
- }
+ if (link) {
+ itemLinks.push(link);
+ }
- return (
-
- {iconData && (
-
-
-
- )}
-
- {itemTitle &&
- React.createElement(
- itemTitleHeadingTag,
- {
- className: b('item-title'),
- },
-
- {itemTitle}
- {label && (
-
- {label}
-
- )}
- ,
+ return (
+
+ {iconData && (
+
+
+
)}
-
-
-
- );
- },
- )}
-
-
+
+ {itemTitle &&
+ React.createElement(
+ itemTitleHeadingTag,
+ {
+ className: b('item-title'),
+ },
+
+ {itemTitle}
+ {label && (
+
+ {label}
+
+ )}
+ ,
+ )}
+
+
+
+ );
+ },
+ )}
+
+
+
);
};
diff --git a/src/blocks/ExtendedFeatures/index.ts b/src/blocks/ExtendedFeatures/index.ts
new file mode 100644
index 000000000..07db00ba6
--- /dev/null
+++ b/src/blocks/ExtendedFeatures/index.ts
@@ -0,0 +1,82 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import ExtendedFeaturesBlock from './ExtendedFeatures';
+import {ExtendedFeaturesBlock as ExtendedFeaturesBlockSchema} from './schema';
+
+const ExtendedFeaturesBlockConfig = {
+ component: ExtendedFeaturesBlock,
+ schema: {
+ name: 'Extended Features Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(
+ ExtendedFeaturesBlockSchema['extended-features-block'] as unknown as JSONSchemaType<{}>,
+ ),
+ default: {
+ type: 'extended-features-block',
+ title: {
+ text: 'Lorem ipsum dolor sit amet',
+ textSize: 'm',
+ },
+ description:
+ 'Three cards in a row on the desktop, two cards in a row on a tablet, one card in a row on a mobile phone.',
+ items: [
+ {
+ title: 'Sed do eiusmod tempor incididunt',
+ text: 'Ut enim ad minim veniam quis nostrud ullamco laboris nisi ut aliquip ex ea commodo consequat.',
+ additionalInfo:
+ 'Duis aute irure dolor in reprehenderit n voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
+ },
+ {
+ title: 'Sed do eiusmod tempor',
+ text: 'Ut enim ad minim veniam quis nostrud ullamco laboris nisi ut aliquip ex ea commodo consequat.',
+ buttons: [
+ {
+ text: 'Button',
+ theme: 'action',
+ url: 'https://example.com',
+ },
+ {
+ text: 'Button',
+ theme: 'outlined',
+ url: 'https://example.com',
+ },
+ ],
+ },
+ {
+ title: 'Sed do eiusmod tempor incididunt',
+ text: 'Ut enim ad minim veniam quis nostrud ullamco laboris nisi ut aliquip ex ea commodo consequat.',
+ links: [
+ {
+ text: 'Go',
+ url: '#',
+ arrow: true,
+ theme: 'normal',
+ },
+ ],
+ },
+ {
+ title: 'Sed do eiusmod tempor incididunt',
+ text: 'Ut enim ad minim veniam quis nostrud ullamco laboris nisi ut aliquip ex ea commodo consequat.',
+ list: [
+ {
+ title: 'Lorem ipsum',
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ },
+ {
+ title: 'Lorem ipsum ipsum',
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ },
+ {
+ title: 'Lorem ipsum',
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ },
+ ],
+ },
+ ],
+ },
+ },
+};
+
+export default ExtendedFeaturesBlockConfig;
diff --git a/src/blocks/ExtendedFeatures/schema.ts b/src/blocks/ExtendedFeatures/schema.ts
index a7705dfc1..eae9ea2e5 100644
--- a/src/blocks/ExtendedFeatures/schema.ts
+++ b/src/blocks/ExtendedFeatures/schema.ts
@@ -20,6 +20,7 @@ export const ExtendedFeaturesItem = {
text: {
type: 'string',
contentType: 'yfm',
+ inputType: 'textarea',
},
label: {
type: 'string',
diff --git a/src/blocks/FilterBlock/FilterBlock.tsx b/src/blocks/FilterBlock/FilterBlock.tsx
index 2f6439223..6563b8a0a 100644
--- a/src/blocks/FilterBlock/FilterBlock.tsx
+++ b/src/blocks/FilterBlock/FilterBlock.tsx
@@ -4,7 +4,7 @@ import {CardLayoutBlock} from '..';
import {AnimateBlock, Title} from '../../components';
import ButtonTabs, {ButtonTabsItemProps} from '../../components/ButtonTabs/ButtonTabs';
import {ConstructorItem} from '../../containers/PageConstructor/components/ConstructorItem';
-import {Col, Row} from '../../grid';
+import {Col, Grid, Row} from '../../grid';
import {FilterBlockProps, FilterItem} from '../../models';
import {block, getBlockKey} from '../../utils';
@@ -19,7 +19,7 @@ const FilterBlock: React.FC
= ({
tags,
tagButtonSize,
allTag,
- items,
+ items = [],
colSizes,
centered,
animated,
@@ -53,35 +53,36 @@ const FilterBlock: React.FC = ({
return (
- {title && (
-
- )}
- {tabButtons.length && (
-
-
-
-
+
+ {title && (
+
+ )}
+ {tabButtons.length && (
+
+
+
+
+
+ )}
+
+
+ {cards.map((card, index) => {
+ const key = getBlockKey(card, index);
+ return ;
+ })}
+
- )}
-
-
- {cards.map((card, index) => {
- const key = getBlockKey(card, index);
-
- return ;
- })}
-
-
+
);
};
diff --git a/src/blocks/FilterBlock/index.ts b/src/blocks/FilterBlock/index.ts
new file mode 100644
index 000000000..525887680
--- /dev/null
+++ b/src/blocks/FilterBlock/index.ts
@@ -0,0 +1,100 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import FilterBlock from './FilterBlock';
+import {FilterProps} from './schema';
+
+const FilterBlockConfig = {
+ component: FilterBlock,
+ schema: {
+ name: 'Filter Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(FilterProps as unknown as JSONSchemaType<{}>),
+ default: {
+ allTag: true,
+ description:
+ 'Three cards in a row on the desktop, two cards in a row on a tablet, one card in a row on a mobile phone.',
+ items: [
+ {
+ card: {
+ content: {
+ title: 'Layout Item 1',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ type: 'layout-item',
+ },
+ tags: ['one'],
+ },
+ {
+ card: {
+ content: {
+ title: 'Layout Item 2',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ type: 'layout-item',
+ },
+ tags: ['two'],
+ },
+ {
+ card: {
+ content: {
+ title: 'Layout Item 3',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ type: 'layout-item',
+ },
+ tags: ['three'],
+ },
+ {
+ card: {
+ content: {
+ title: 'Layout Item 4',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ type: 'layout-item',
+ },
+ tags: ['one'],
+ },
+ {
+ card: {
+ content: {
+ title: 'Layout Item 5',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ type: 'layout-item',
+ },
+ tags: ['two'],
+ },
+ {
+ card: {
+ content: {
+ title: 'Layout Item 6',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ type: 'layout-item',
+ },
+ tags: ['three'],
+ },
+ ],
+ tags: [
+ {
+ id: 'one',
+ label: 'First very long label',
+ },
+ {
+ id: 'two',
+ label: 'Second very long label',
+ },
+ {
+ id: 'three',
+ label: 'Third very long label',
+ },
+ ],
+ title: 'Card Layout',
+ type: 'filter-block',
+ },
+ },
+};
+
+export default FilterBlockConfig;
diff --git a/src/blocks/Form/index.ts b/src/blocks/Form/index.ts
new file mode 100644
index 000000000..12a54bb5b
--- /dev/null
+++ b/src/blocks/Form/index.ts
@@ -0,0 +1,21 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import FormBlock from './Form';
+import {FormBlock as FormBlockSchema} from './schema';
+
+const FormBlockConfig = {
+ component: FormBlock,
+ schema: {
+ name: 'Form Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(FormBlockSchema['form-block'] as unknown as JSONSchemaType<{}>),
+ default: {
+ title: 'Form Block',
+ formData: {},
+ },
+ },
+};
+
+export default FormBlockConfig;
diff --git a/src/blocks/Header/dynamic-form.ts b/src/blocks/Header/dynamic-form.ts
new file mode 100644
index 000000000..c76efcdc2
--- /dev/null
+++ b/src/blocks/Header/dynamic-form.ts
@@ -0,0 +1,732 @@
+import {BlockConfig, ConfigInput} from '../../form-generator';
+import {imageInputs} from '../../components/Image/dynamic-form';
+import {Theme} from '../../models';
+
+const mediaInputs: ConfigInput[] = [
+ {
+ type: 'oneOf',
+ name: '',
+ key: 'mediaType',
+ title: 'Media Type',
+ options: [
+ {
+ title: 'Empty',
+ value: 'emptyMedia',
+ properties: [
+ {
+ type: 'text',
+ name: 'color',
+ title: 'Color',
+ },
+ ],
+ },
+ {
+ title: 'Image',
+ value: 'image',
+ properties: [
+ {
+ type: 'oneOf',
+ name: 'image',
+ key: 'imagesCount',
+ title: 'Images Count',
+ options: [
+ {title: 'Single Image', value: 'singleImage', properties: imageInputs},
+ {
+ title: 'Multiple Images',
+ value: 'multipleImages',
+ properties: [
+ {
+ type: 'array',
+ name: '',
+ arrayType: 'object',
+ title: 'Images',
+ properties: imageInputs,
+ buttonText: 'Add Image',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ title: 'Youtube',
+ value: 'youtube',
+ properties: [
+ {
+ type: 'text',
+ name: 'youtube',
+ title: 'Youtube',
+ },
+ {
+ type: 'text',
+ name: 'previewImg',
+ title: 'Preview Image',
+ },
+ ],
+ },
+ {
+ title: 'Datalens',
+ value: 'datalens',
+ properties: [],
+ },
+ {
+ title: 'Iframe',
+ value: 'iframe',
+ properties: [],
+ },
+ {
+ title: 'Video',
+ value: 'video',
+ properties: [],
+ },
+ ],
+ },
+ {
+ type: 'boolean',
+ name: 'disableImageSliderForArrayInput',
+ title: 'Disable Image Slider For Array Input',
+ },
+ {
+ type: 'boolean',
+ name: 'parallax',
+ title: 'Parallax',
+ },
+ {
+ type: 'number',
+ name: 'height',
+ title: 'Height',
+ },
+ {
+ type: 'boolean',
+ name: 'fullscreen',
+ title: 'Fullscreen',
+ },
+ {
+ type: 'number',
+ name: 'ratio',
+ title: 'Ratio',
+ },
+ {
+ type: 'boolean',
+ name: 'margins',
+ title: 'Margins',
+ },
+];
+
+const headerBackgroundInputs: ConfigInput[] = [
+ {
+ type: 'boolean',
+ name: 'fullWidthMedia',
+ title: 'Full width Media',
+ },
+ {
+ type: 'boolean',
+ name: 'fullWidth',
+ title: 'Full width',
+ },
+];
+
+const backgroundProperty = (properties: ConfigInput[]): ConfigInput => ({
+ type: 'oneOf',
+ name: 'background',
+ title: 'Background',
+ options: [
+ {
+ value: 'noThemes',
+ title: 'No Themes',
+ properties: properties,
+ },
+ {
+ value: 'withThemes',
+ title: 'With Themes',
+ properties: Object.values(Theme).map((theme) => ({
+ type: 'object',
+ name: theme,
+ title: theme,
+ properties: properties,
+ })),
+ },
+ ],
+});
+
+export const blockConfig: BlockConfig = {
+ name: 'Header Block',
+ inputs: [
+ {
+ type: 'text',
+ name: 'title',
+ title: 'Title',
+ },
+ {
+ type: 'text',
+ name: 'overtitle',
+ title: 'Overtitle',
+ },
+ {
+ type: 'textarea',
+ name: 'description',
+ title: 'Description',
+ },
+ backgroundProperty([...mediaInputs, ...headerBackgroundInputs]),
+ /* {
+ type: 'text',
+ name: 'when',
+ title: 'when',
+ },
+ {
+ type: 'object',
+ name: 'anchor',
+ title: 'anchor',
+ properties: [
+ {
+ type: 'text',
+ name: 'text',
+ title: 'text',
+ },
+ {
+ type: 'text',
+ name: 'url',
+ title: 'url',
+ },
+ {
+ type: 'text',
+ name: 'urlTitle',
+ title: 'urlTitle',
+ },
+ ],
+ },
+ {
+ type: 'select',
+ name: 'visible',
+ title: 'visible',
+ enum: [
+ {
+ content: 'sm',
+ value: 'sm',
+ },
+ {
+ content: 'md',
+ value: 'md',
+ },
+ {
+ content: 'lg',
+ value: 'lg',
+ },
+ {
+ content: 'xl',
+ value: 'xl',
+ },
+ {
+ content: 'all',
+ value: 'all',
+ },
+ ],
+ },
+ {
+ type: 'text',
+ name: 'context',
+ title: 'context',
+ },
+ {
+ type: 'object',
+ name: 'indent',
+ title: 'indent',
+ properties: [],
+ },
+ {
+ type: 'select',
+ name: 'width',
+ title: 'width',
+ enum: [
+ {
+ content: 's',
+ value: 's',
+ },
+ {
+ content: 'm',
+ value: 'm',
+ },
+ {
+ content: 'l',
+ value: 'l',
+ },
+ ],
+ },
+ {
+ type: 'array',
+ name: 'buttons',
+ title: 'buttons',
+ properties: [
+ {
+ type: 'text',
+ name: 'when',
+ title: 'when',
+ },
+ {
+ type: 'text',
+ name: 'text',
+ title: 'text',
+ },
+ {
+ type: 'text',
+ name: 'url',
+ title: 'url',
+ },
+ {
+ type: 'text',
+ name: 'urlTitle',
+ title: 'urlTitle',
+ },
+ {
+ type: 'select',
+ name: 'size',
+ title: 'size',
+ enum: [
+ {
+ content: 'xs',
+ value: 'xs',
+ },
+ {
+ content: 'ns',
+ value: 'ns',
+ },
+ {
+ content: 's',
+ value: 's',
+ },
+ {
+ content: 'n',
+ value: 'n',
+ },
+ {
+ content: 'm',
+ value: 'm',
+ },
+ {
+ content: 'l',
+ value: 'l',
+ },
+ {
+ content: 'xl',
+ value: 'xl',
+ },
+ {
+ content: 'head',
+ value: 'head',
+ },
+ {
+ content: 'promo',
+ value: 'promo',
+ },
+ ],
+ },
+ {
+ type: 'select',
+ name: 'theme',
+ title: 'theme',
+ enum: [
+ {
+ content: 'normal',
+ value: 'normal',
+ },
+ {
+ content: 'action',
+ value: 'action',
+ },
+ {
+ content: 'outlined',
+ value: 'outlined',
+ },
+ {
+ content: 'outlined-info',
+ value: 'outlined-info',
+ },
+ {
+ content: 'outlined-danger',
+ value: 'outlined-danger',
+ },
+ {
+ content: 'raised',
+ value: 'raised',
+ },
+ {
+ content: 'flat',
+ value: 'flat',
+ },
+ {
+ content: 'flat-info',
+ value: 'flat-info',
+ },
+ {
+ content: 'flat-danger',
+ value: 'flat-danger',
+ },
+ {
+ content: 'flat-secondary',
+ value: 'flat-secondary',
+ },
+ {
+ content: 'clear',
+ value: 'clear',
+ },
+ {
+ content: 'normal-contrast',
+ value: 'normal-contrast',
+ },
+ {
+ content: 'outlined-contrast',
+ value: 'outlined-contrast',
+ },
+ {
+ content: 'flat-contrast',
+ value: 'flat-contrast',
+ },
+ {
+ content: 'link',
+ value: 'link',
+ },
+ {
+ content: 'pseudo',
+ value: 'pseudo',
+ },
+ {
+ content: 'pseudo-special',
+ value: 'pseudo-special',
+ },
+ {
+ content: 'websearch',
+ value: 'websearch',
+ },
+ {
+ content: 'normal-dark',
+ value: 'normal-dark',
+ },
+ {
+ content: 'normal-special',
+ value: 'normal-special',
+ },
+ {
+ content: 'accent',
+ value: 'accent',
+ },
+ {
+ content: 'dark-grey',
+ value: 'dark-grey',
+ },
+ {
+ content: 'app-store',
+ value: 'app-store',
+ },
+ {
+ content: 'google-play',
+ value: 'google-play',
+ },
+ {
+ content: 'scale',
+ value: 'scale',
+ },
+ {
+ content: 'github',
+ value: 'github',
+ },
+ {
+ content: 'monochrome',
+ value: 'monochrome',
+ },
+ ],
+ },
+ {
+ type: 'oneOf',
+ name: 'img',
+ title: 'img',
+ options: [
+ {
+ value: 'url',
+ title: 'url',
+ properties: [],
+ },
+ {
+ value: 'options',
+ title: 'options',
+ properties: [],
+ },
+ ],
+ },
+ {
+ type: 'oneOf',
+ name: 'analyticsEvents',
+ title: 'analyticsEvents',
+ options: [
+ {
+ value: 'single',
+ title: 'single',
+ properties: [
+ {
+ type: 'text',
+ name: 'additionalProperties',
+ title: 'additionalProperties',
+ },
+ ],
+ },
+ {
+ value: 'list',
+ title: 'list',
+ properties: [
+ {
+ type: 'object',
+ name: 'items',
+ title: 'items',
+ properties: [
+ {
+ type: 'text',
+ name: 'name',
+ title: 'name',
+ },
+ {
+ type: 'text',
+ name: 'type',
+ title: 'type',
+ },
+ {
+ type: 'object',
+ name: 'counters',
+ title: 'counters',
+ properties: [],
+ },
+ {
+ type: 'text',
+ name: 'context',
+ title: 'context',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'select',
+ name: 'target',
+ title: 'target',
+ enum: [
+ {
+ content: '_self',
+ value: '_self',
+ },
+ {
+ content: '_blank',
+ value: '_blank',
+ },
+ {
+ content: '_parent',
+ value: '_parent',
+ },
+ {
+ content: '_top',
+ value: '_top',
+ },
+ ],
+ },
+ {
+ type: 'select',
+ name: 'width',
+ title: 'width',
+ enum: [
+ {
+ content: 'auto',
+ value: 'auto',
+ },
+ {
+ content: 'max',
+ value: 'max',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'select',
+ name: 'offset',
+ title: 'offset',
+ enum: [
+ {
+ content: 'default',
+ value: 'default',
+ },
+ {
+ content: 'large',
+ value: 'large',
+ },
+ ],
+ },
+ {
+ type: 'oneOf',
+ name: 'image',
+ title: 'image',
+ options: [
+ {
+ value: 'no theme',
+ title: 'no theme',
+ properties: [],
+ },
+ {
+ value: 'themes',
+ title: 'themes',
+ properties: [],
+ },
+ ],
+ },
+ {
+ type: 'oneOf',
+ name: 'video',
+ title: 'video',
+ options: [
+ {
+ value: 'no theme',
+ title: 'no theme',
+ properties: [],
+ },
+ {
+ value: 'themes',
+ title: 'themes',
+ properties: [],
+ },
+ ],
+ },
+ {
+ type: 'select',
+ name: 'mediaView',
+ title: 'mediaView',
+ enum: [
+ {
+ content: 'fit',
+ value: 'fit',
+ },
+ {
+ content: 'full',
+ value: 'full',
+ },
+ ],
+ },
+ {
+ type: 'object',
+ name: 'backLink',
+ title: 'backLink',
+ properties: [
+ {
+ type: 'text',
+ name: 'url',
+ title: 'url',
+ },
+ {
+ type: 'text',
+ name: 'title',
+ title: 'title',
+ },
+ ],
+ },
+ {
+ type: 'select',
+ name: 'imageSize',
+ title: 'imageSize',
+ enum: [
+ {
+ content: 's',
+ value: 's',
+ },
+ {
+ content: 'm',
+ value: 'm',
+ },
+ ],
+ },
+ {
+ type: 'select',
+ name: 'verticalOffset',
+ title: 'verticalOffset',
+ enum: [
+ {
+ content: '0',
+ value: '0',
+ },
+ {
+ content: 's',
+ value: 's',
+ },
+ {
+ content: 'm',
+ value: 'm',
+ },
+ {
+ content: 'l',
+ value: 'l',
+ },
+ {
+ content: 'xl',
+ value: 'xl',
+ },
+ ],
+ },
+
+ {
+ type: 'select',
+ name: 'theme',
+ title: 'theme',
+ enum: [
+ {
+ content: 'default',
+ value: 'default',
+ },
+ {
+ content: 'dark',
+ value: 'dark',
+ },
+ ],
+ },
+ {
+ type: 'object',
+ name: 'breadcrumbs',
+ title: 'breadcrumbs',
+ properties: [
+ {
+ type: 'array',
+ name: 'items',
+ title: 'items',
+ properties: [
+ {
+ type: 'text',
+ name: 'url',
+ title: 'url',
+ },
+ {
+ type: 'text',
+ name: 'text',
+ title: 'text',
+ },
+ ],
+ },
+ {
+ type: 'select',
+ name: 'theme',
+ title: 'theme',
+ enum: [
+ {
+ content: 'light',
+ value: 'light',
+ },
+ {
+ content: 'dark',
+ value: 'dark',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'text',
+ name: 'status',
+ title: 'status',
+ },*/
+ ],
+};
diff --git a/src/blocks/Header/index.ts b/src/blocks/Header/index.ts
new file mode 100644
index 000000000..df7b962ad
--- /dev/null
+++ b/src/blocks/Header/index.ts
@@ -0,0 +1,36 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import HeaderBlock from './Header';
+import {HeaderBlock as HeaderBlockSchema} from './schema';
+
+const HeaderBlockConfig = {
+ component: HeaderBlock,
+ schema: {
+ name: 'Header Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(HeaderBlockSchema['header-block'] as unknown as JSONSchemaType<{}>),
+ default: {
+ type: 'header-block',
+ title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
+ description:
+ 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
+ buttons: [
+ {
+ text: 'Button\r',
+ theme: 'action',
+ url: 'https://example.com',
+ },
+ {
+ text: 'Button',
+ theme: 'outlined',
+ url: 'https://example.com',
+ },
+ ],
+ },
+ previewImg: 'https://storage.cloud-preprod.yandex.net/qradle-test/header-block.svg',
+ },
+};
+
+export default HeaderBlockConfig;
diff --git a/src/blocks/HeaderSlider/HeaderSlider.tsx b/src/blocks/HeaderSlider/HeaderSlider.tsx
index 02e0c11d2..c621ae7e4 100644
--- a/src/blocks/HeaderSlider/HeaderSlider.tsx
+++ b/src/blocks/HeaderSlider/HeaderSlider.tsx
@@ -25,15 +25,16 @@ export const HeaderSliderBlock = ({items, arrows, ...props}: HeaderSliderBlockPr
blockClassName={b()}
arrowSize={20}
>
- {items.map((item, index) => (
-
-
-
- ))}
+ {items &&
+ items.map((item, index) => (
+
+
+
+ ))}
);
diff --git a/src/blocks/HeaderSlider/index.ts b/src/blocks/HeaderSlider/index.ts
new file mode 100644
index 000000000..2d98982e2
--- /dev/null
+++ b/src/blocks/HeaderSlider/index.ts
@@ -0,0 +1,45 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import HeaderSliderBlock from './HeaderSlider';
+import {HeaderSliderBlock as HeaderSliderBlockSchema} from './schema';
+
+const HeaderSliderBlockConfig = {
+ component: HeaderSliderBlock,
+ schema: {
+ name: 'Header Slider Block',
+ group: '@gravity-ui/page-constructor/Card Containers',
+ inputs: generateFromAJV(
+ HeaderSliderBlockSchema['header-slider-block'] as unknown as JSONSchemaType<{}>,
+ ),
+ default: {
+ type: 'header-slider-block',
+ items: [
+ {
+ title: 'Header Slide 1',
+ overtitle: 'Header Slider Block presents',
+ description:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ {
+ image: 'https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/header-bg-video_light.png',
+ mediaView: 'fit',
+ title: 'Header Slide 2',
+ overtitle: 'Header Slider Block presents',
+ description:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ width: 'm',
+ buttons: [
+ {
+ text: 'Button',
+ theme: 'action',
+ },
+ ],
+ },
+ ],
+ },
+ },
+};
+
+export default HeaderSliderBlockConfig;
diff --git a/src/blocks/Icons/Icons.scss b/src/blocks/Icons/Icons.scss
index d098159d6..bd0cc7d3e 100644
--- a/src/blocks/Icons/Icons.scss
+++ b/src/blocks/Icons/Icons.scss
@@ -48,6 +48,7 @@ $block: '.#{$ns}icons-block';
@include reset-link-style();
margin: 0 $indentXXXS $indentSM;
}
+
a#{$block}__item {
@include focusable();
border-radius: var(--g-focus-border-radius);
diff --git a/src/blocks/Icons/Icons.tsx b/src/blocks/Icons/Icons.tsx
index e6ce8c667..8d966d4aa 100644
--- a/src/blocks/Icons/Icons.tsx
+++ b/src/blocks/Icons/Icons.tsx
@@ -3,6 +3,7 @@ import * as React from 'react';
import {Image, Title} from '../../components';
import {LocationContext} from '../../context/locationContext';
import {useTheme} from '../../context/theme';
+import {Grid} from '../../grid';
import {useAnalytics} from '../../hooks';
import {IconsBlockItemProps, IconsBlockProps} from '../../models';
import {block, getLinkProps, getThemedValue} from '../../utils';
@@ -31,38 +32,41 @@ const Icons = ({title, description, size = 's', colSizes = {all: 12}, items}: Ic
);
return (
-
- {(title || description) && (
-
- )}
- {items.map((item) => {
- const themedSrc = getThemedValue(item.src, theme);
- const itemContent = getItemContent({...item, src: themedSrc});
- const {url, text} = item;
- return url ? (
-
onClick(item)}
- >
- {itemContent}
-
- ) : (
-
- {itemContent}
-
- );
- })}
-
+
+
+ {(title || description) && (
+
+ )}
+ {items &&
+ items.map((item) => {
+ const themedSrc = getThemedValue(item.src, theme);
+ const itemContent = getItemContent({...item, src: themedSrc});
+ const {url, text} = item;
+ return url ? (
+
onClick(item)}
+ >
+ {itemContent}
+
+ ) : (
+
+ {itemContent}
+
+ );
+ })}
+
+
);
};
diff --git a/src/blocks/Icons/index.ts b/src/blocks/Icons/index.ts
new file mode 100644
index 000000000..92721c830
--- /dev/null
+++ b/src/blocks/Icons/index.ts
@@ -0,0 +1,66 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import IconsBlock from './Icons';
+import {IconsProps} from './schema';
+
+const IconsBlockConfig = {
+ component: IconsBlock,
+ schema: {
+ name: 'Icons Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(IconsProps as unknown as JSONSchemaType<{}>),
+ default: {
+ type: 'icons-block',
+ title: 'Icons Block',
+ description:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ size: 'm',
+ items: [
+ {
+ url: '/security/standards/software-registry',
+ text: 'Государственные реестры РФ',
+ src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-software-registry.svg',
+ },
+ {
+ url: '/security/standards/gdpr',
+ text: 'Общий регламент защиты данных (GDPR)',
+ src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-gdpr.svg',
+ },
+ {
+ url: '/security/standards/cloud-security-alliance',
+ text: 'Cloud Security Alliance',
+ src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-csa.svg',
+ },
+ {
+ url: '/security/standards/iso-standards',
+ text: 'Международная организация по стандартизации (ISO)',
+ src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-iso.svg',
+ },
+ {
+ url: '/security/standards/152-fz',
+ text: '№152-ФЗ «О персональных данных»',
+ src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-152-fz.svg',
+ },
+ {
+ url: '/security/standards/gost-p-57580',
+ text: 'ГОСТ Р 57580',
+ src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-gost.svg',
+ },
+ {
+ url: '/security/standards/pci',
+ text: 'Payment Card Industry Data Security Standard',
+ src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/main-shield/security-pci.svg',
+ },
+ {
+ url: '/docs/security/standard/all',
+ text: 'Стандарт по защите облачной инфраструктуры',
+ src: 'https://storage.yandexcloud.net/cloud-www-assets/security-new/icons/security_yc.svg',
+ },
+ ],
+ },
+ },
+};
+
+export default IconsBlockConfig;
diff --git a/src/blocks/Info/index.ts b/src/blocks/Info/index.ts
new file mode 100644
index 000000000..a97b77554
--- /dev/null
+++ b/src/blocks/Info/index.ts
@@ -0,0 +1,44 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import InfoBlock from './Info';
+import {InfoBlock as InfoBlockSchema} from './schema';
+
+const InfoBlockConfig = {
+ component: InfoBlock,
+ schema: {
+ name: 'Info Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(InfoBlockSchema['info-block'] as unknown as JSONSchemaType<{}>),
+ default: {
+ type: 'info-block',
+ title: 'Info Block',
+ backgroundColor: '#1c1c1c',
+ sectionsTitle: 'Other links',
+ links: [
+ {
+ text: 'Link 1',
+ },
+ {
+ text: 'Link 2',
+ },
+ {
+ text: 'Link 3',
+ },
+ ],
+ buttons: [
+ {
+ text: 'Read more',
+ theme: 'outlined-contrast',
+ },
+ {
+ text: 'Go back',
+ theme: 'action',
+ },
+ ],
+ },
+ },
+};
+
+export default InfoBlockConfig;
diff --git a/src/blocks/Map/index.ts b/src/blocks/Map/index.ts
new file mode 100644
index 000000000..a73df628b
--- /dev/null
+++ b/src/blocks/Map/index.ts
@@ -0,0 +1,20 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import MapBlock from './Map';
+import {MapBlock as MapBlockSchema} from './schema';
+
+const MapBlockConfig = {
+ component: MapBlock,
+ schema: {
+ name: 'Map Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(MapBlockSchema['map-block'] as unknown as JSONSchemaType<{}>),
+ default: {
+ title: 'Map Block',
+ },
+ },
+};
+
+export default MapBlockConfig;
diff --git a/src/blocks/Media/index.ts b/src/blocks/Media/index.ts
new file mode 100644
index 000000000..78ca8a512
--- /dev/null
+++ b/src/blocks/Media/index.ts
@@ -0,0 +1,60 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import MediaBlock from './Media';
+import {MediaBlock as MediaBlockSchema} from './schema';
+
+const MediaBlockConfig = {
+ component: MediaBlock,
+ schema: {
+ name: 'Media Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(MediaBlockSchema['media-block'] as unknown as JSONSchemaType<{}>),
+ default: {
+ title: 'Lorem ipsum dolor sit',
+ description:
+ 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
+ additionalInfo:
+ 'Duis aute irure dolor in reprehenderit n voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
+ links: [
+ {
+ url: '#',
+ text: 'Learn more',
+ theme: 'normal',
+ arrow: true,
+ },
+ ],
+ buttons: [
+ {
+ text: 'Button',
+ theme: 'action',
+ url: 'https://example.com',
+ },
+ {
+ text: 'Button',
+ theme: 'outlined',
+ url: '#',
+ },
+ ],
+ list: [
+ {
+ title: 'Lorem ipsum',
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ },
+ {
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ },
+ {
+ title: 'Lorem ipsum ipsum',
+ },
+ ],
+ media: {
+ image: 'https://storage.yandexcloud.net/cloud-www-assets/constructor/main/new/media-01-01.jpg',
+ },
+ },
+ previewImg: 'https://storage.cloud-preprod.yandex.net/qradle-test/media-block.svg',
+ },
+};
+
+export default MediaBlockConfig;
diff --git a/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx b/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx
index 4c34e6a95..227a9d331 100644
--- a/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx
+++ b/src/blocks/PromoFeaturesBlock/PromoFeaturesBlock.tsx
@@ -6,6 +6,7 @@ import Title from '../../components/Title/Title';
import YFMWrapper from '../../components/YFMWrapper/YFMWrapper';
import {BREAKPOINTS} from '../../constants';
import {useTheme} from '../../context/theme';
+import {Grid} from '../../grid';
import {PromoFeaturesProps} from '../../models';
import {block, getThemedValue} from '../../utils';
import {mergeVideoMicrodata} from '../../utils/microdata';
@@ -26,43 +27,50 @@ const PromoFeaturesBlock = (props: PromoFeaturesProps) => {
const globalTheme = useTheme();
return (
-
-
-
-
- {items.map(({title: cardTitle, text, media, theme: cardTheme}, index) => {
- const blockModifier = backgroundTheme === 'default' ? 'default' : 'light';
- const themeMod = cardTheme || blockModifier || '';
- const themedMedia = getThemedValue(media, globalTheme);
- const allProps = mergeVideoMicrodata(themedMedia, {
- name: cardTitle,
- description: text,
- });
+
+
+
+
+
+ {items &&
+ items.map(({title: cardTitle, text, media, theme: cardTheme}, index) => {
+ const blockModifier =
+ backgroundTheme === 'default' ? 'default' : 'light';
+ const themeMod = cardTheme || blockModifier || '';
+ const themedMedia = getThemedValue(media, globalTheme);
+ const allProps = mergeVideoMicrodata(themedMedia, {
+ name: cardTitle,
+ description: text,
+ });
- return (
-
-
-
{cardTitle}
-
- {media &&
}
-
- );
- })}
-
-
+ );
+ })}
+
+
+
);
};
diff --git a/src/blocks/PromoFeaturesBlock/index.ts b/src/blocks/PromoFeaturesBlock/index.ts
new file mode 100644
index 000000000..c2436c3d4
--- /dev/null
+++ b/src/blocks/PromoFeaturesBlock/index.ts
@@ -0,0 +1,52 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import PromoFeaturesBlock from './PromoFeaturesBlock';
+import {PromoFeaturesBlock as PromoFeaturesBlockSchema} from './schema';
+
+const PromoFeaturesBlockConfig = {
+ component: PromoFeaturesBlock,
+ schema: {
+ name: 'Promo Features Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(
+ PromoFeaturesBlockSchema['promo-features-block'] as unknown as JSONSchemaType<{}>,
+ ),
+ default: {
+ title: 'Promo Features Block',
+ theme: 'default',
+ items: [
+ {
+ title: 'Lorem ipsum dolor sit amet',
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ },
+ {
+ title: 'Lorem ipsum dolor sit amet',
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ },
+ {
+ title: 'Lorem ipsum dolor sit amet',
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ },
+ {
+ title: 'Lorem ipsum dolor sit amet',
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ theme: 'accent',
+ },
+ {
+ title: 'Lorem ipsum dolor sit amet',
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ theme: 'accent-light',
+ },
+ {
+ title: 'Lorem ipsum dolor sit amet',
+ text: 'Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ theme: 'primary',
+ },
+ ],
+ },
+ },
+};
+
+export default PromoFeaturesBlockConfig;
diff --git a/src/blocks/Questions/Questions.tsx b/src/blocks/Questions/Questions.tsx
index dc70729e7..0f4b5840c 100644
--- a/src/blocks/Questions/Questions.tsx
+++ b/src/blocks/Questions/Questions.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
-import {Col, Row} from '../../grid';
+import {Col, Grid, Row} from '../../grid';
import {QuestionsProps} from '../../models';
import {Content} from '../../sub-blocks';
import {block} from '../../utils';
@@ -29,48 +29,54 @@ const QuestionsBlock = (props: QuestionsProps) => {
};
return (
-
-
-
-
-
-
-
-
- {items.map(
- ({title: itemTitle, text: itemText, link, listStyle = 'dash'}, index) => {
- const isOpened = opened.includes(index);
- const onClick = () => toggleItem(index);
+
+
+
+
+
+
+
+
+
+ {items &&
+ items.map(
+ (
+ {title: itemTitle, text: itemText, link, listStyle = 'dash'},
+ index,
+ ) => {
+ const isOpened = opened.includes(index);
+ const onClick = () => toggleItem(index);
- return (
-
- );
- },
- )}
-
-
-
+ return (
+
+ );
+ },
+ )}
+
+
+
+
);
};
diff --git a/src/blocks/Questions/index.ts b/src/blocks/Questions/index.ts
new file mode 100644
index 000000000..db781a918
--- /dev/null
+++ b/src/blocks/Questions/index.ts
@@ -0,0 +1,52 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import QuestionsBlock from './Questions';
+import {QuestionsBlock as QuestionsBlockSchema} from './schema';
+
+const QuestionsBlockConfig = {
+ component: QuestionsBlock,
+ schema: {
+ name: 'Questions Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(
+ QuestionsBlockSchema['questions-block'] as unknown as JSONSchemaType<{}>,
+ ),
+ default: {
+ type: 'questions-block',
+ title: 'Questions Block',
+ text: 'Here you can find answers.',
+ links: [
+ {
+ text: 'Report for 2024',
+ url: 'file.doc',
+ },
+ ],
+ buttons: [
+ {
+ text: 'Get more',
+ theme: 'outlined-info',
+ },
+ ],
+ list: [
+ {
+ title: 'Report for 2024',
+ text: 'Some advice here',
+ },
+ ],
+ items: [
+ {
+ title: 'Question 1',
+ text: 'Answer for question 1',
+ },
+ {
+ title: 'Question 2',
+ text: 'Answer for question 2',
+ },
+ ],
+ },
+ },
+};
+
+export default QuestionsBlockConfig;
diff --git a/src/blocks/Share/Share.tsx b/src/blocks/Share/Share.tsx
index f3450836b..8a911261b 100644
--- a/src/blocks/Share/Share.tsx
+++ b/src/blocks/Share/Share.tsx
@@ -40,31 +40,34 @@ const Share = ({items, title}: ShareBlockProps) => {
{title || i18n('constructor-share')}
- {items.map((type) => {
- const url = getAbsolutePath(hostname, pathname);
- const socialUrl = getShareLink(url, type);
- const icon = icons[type];
- const urlTitle = i18n(`${type}-title`);
- const buttonLabel = i18n(`${type}-label`);
+ {items &&
+ items.map((type) => {
+ const url = getAbsolutePath(hostname, pathname);
+ const socialUrl = getShareLink(url, type);
+ const icon = icons[type];
+ const urlTitle = i18n(`${type}-title`);
+ const buttonLabel = i18n(`${type}-label`);
- return (
-
- {icon && }
-
- );
- })}
+ return (
+
+ {icon && (
+
+ )}
+
+ );
+ })}
);
diff --git a/src/blocks/Share/index.ts b/src/blocks/Share/index.ts
new file mode 100644
index 000000000..6c458c374
--- /dev/null
+++ b/src/blocks/Share/index.ts
@@ -0,0 +1,21 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import ShareBlock from './Share';
+import {ShareBlock as ShareBlockSchema} from './schema';
+
+const ShareBlockConfig = {
+ component: ShareBlock,
+ schema: {
+ name: 'Share Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(ShareBlockSchema['share-block'] as unknown as JSONSchemaType<{}>),
+ default: {
+ items: ['vk', 'telegram', 'facebook'],
+ title: 'Share Block',
+ },
+ },
+};
+
+export default ShareBlockConfig;
diff --git a/src/blocks/Slider/Slider.tsx b/src/blocks/Slider/Slider.tsx
index eaadacfac..75b94f7a9 100644
--- a/src/blocks/Slider/Slider.tsx
+++ b/src/blocks/Slider/Slider.tsx
@@ -10,10 +10,12 @@ import Anchor from '../../components/Anchor/Anchor';
import AnimateBlock from '../../components/AnimateBlock/AnimateBlock';
import OutsideClick from '../../components/OutsideClick/OutsideClick';
import Title from '../../components/Title/Title';
+import ItemWrap from '../../components/editor/ItemWrap/ItemWrap';
import {BREAKPOINTS} from '../../constants';
import {MobileContext} from '../../context/mobileContext';
import {SSRContext} from '../../context/ssrContext';
import {StylesContext} from '../../context/stylesContext/StylesContext';
+import {Grid} from '../../grid';
import useFocus from '../../hooks/useFocus';
import {
ClassNameProps,
@@ -97,14 +99,16 @@ export const SliderBlock = (props: React.PropsWithChildren
) => {
const isAutoplayEnabled = autoplaySpeed !== undefined && autoplaySpeed > 0;
const isUserInteractionRef = React.useRef(false);
- const [slidesToShow] = React.useState(
- getSlidesToShowWithDefaults({
- contentLength: childrenCount,
- breakpoints: props.slidesToShow,
- mobileFullscreen: Boolean(
- props.type && Object.values(SliderType).includes(props.type as SliderType),
- ),
- }),
+ const slidesToShow = React.useMemo(
+ () =>
+ getSlidesToShowWithDefaults({
+ contentLength: childrenCount,
+ breakpoints: props.slidesToShow,
+ mobileFullscreen: Boolean(
+ props.type && Object.values(SliderType).includes(props.type as SliderType),
+ ),
+ }),
+ [childrenCount, props.slidesToShow, props.type],
);
const slidesToShowCount = getSlidesToShowCount(slidesToShow);
@@ -424,7 +428,12 @@ export const SliderBlock = (props: React.PropsWithChildren) => {
return (
- {disclosedChildren}
+
+ {React.Children.map(disclosedChildren, (child, index) => (
+ {child}
+ ))}
+
+
{renderDisclaimer()}
{renderNavigation()}
@@ -435,28 +444,30 @@ export const SliderBlock = (props: React.PropsWithChildren
) => {
return (
-
- {anchorId &&
}
-
-
- {renderSlider()}
-
-
+
+
+ {anchorId &&
}
+
+
+ {renderSlider()}
+
+
+
);
};
diff --git a/src/blocks/Slider/__tests__/Slider.test.tsx b/src/blocks/Slider/__tests__/Slider.test.tsx
index 7b8d2f8a6..defcfb704 100644
--- a/src/blocks/Slider/__tests__/Slider.test.tsx
+++ b/src/blocks/Slider/__tests__/Slider.test.tsx
@@ -1,5 +1,6 @@
import {queryHelpers, render} from '@testing-library/react';
+import {PageConstructorProvider} from '../../../containers/PageConstructor';
import {BasicCard} from '../../../sub-blocks';
import Slider from '../Slider';
@@ -14,18 +15,20 @@ const slidesToShowValues = [3, 2, 1];
describe('Slider', () => {
test.each(slidesToShowValues)('Has correct slider labels', async (slidesToShow) => {
const {container} = render(
-
- {Array(CARDS_COUNT)
- .fill(null)
- .map((_, index) => (
-
- ))}
- ,
+
+
+ {Array(CARDS_COUNT)
+ .fill(null)
+ .map((_, index) => (
+
+ ))}
+
+ ,
);
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
diff --git a/src/blocks/Slider/dynamic-form.ts b/src/blocks/Slider/dynamic-form.ts
new file mode 100644
index 000000000..4da00da9c
--- /dev/null
+++ b/src/blocks/Slider/dynamic-form.ts
@@ -0,0 +1,226 @@
+import {BlockConfig, ConfigInput} from '../../form-generator';
+import {sliderSizesArray, textSize} from '../../schema/validators/common';
+
+const textSizeEnum = textSize.map((size) => ({value: size, content: size}));
+
+export const blockConfig: BlockConfig = {
+ name: 'Slider Block',
+ inputs: [
+ {
+ type: 'oneOf',
+ name: 'title',
+ title: 'Title Object',
+ options: [
+ {
+ title: 'Simple',
+ value: 'simple',
+ properties: [
+ {
+ type: 'text',
+ name: '',
+ title: 'Title',
+ },
+ ],
+ },
+ {
+ title: 'Complex',
+ value: 'complex',
+ properties: [
+ {
+ type: 'text',
+ name: 'text',
+ title: 'Title',
+ },
+ {
+ type: 'select',
+ name: 'textSize',
+ title: 'Text Size',
+ enum: textSizeEnum,
+ view: 'select',
+ mode: 'single',
+ },
+ {
+ type: 'text',
+ name: 'url',
+ title: 'Url',
+ },
+ {
+ type: 'text',
+ name: 'urlTitle',
+ title: 'Url',
+ },
+ {
+ type: 'boolean',
+ name: 'resetMargin',
+ title: 'Reset Margin',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'textarea',
+ name: 'description',
+ title: 'Description',
+ },
+ {
+ type: 'boolean',
+ name: 'dots',
+ title: 'With dots',
+ },
+ {
+ type: 'boolean',
+ name: 'arrows',
+ title: 'With Arrows',
+ },
+ {
+ type: 'boolean',
+ name: 'randomOrder',
+ title: 'Random Order',
+ },
+ {
+ type: 'number',
+ name: 'autoplay',
+ title: 'Autoplay',
+ },
+ {
+ type: 'object',
+ name: 'disclaimer',
+ title: 'Disclaimer',
+ properties: [
+ {
+ type: 'text',
+ name: 'text',
+ title: 'Text',
+ },
+ {
+ type: 'select',
+ name: 'size',
+ title: 'Size',
+ enum: textSizeEnum,
+ view: 'select',
+ mode: 'single',
+ },
+ ],
+ },
+ {
+ type: 'oneOf',
+ name: 'slidesToShow',
+ title: 'Slides to Show',
+ options: [
+ {
+ title: 'Simple',
+ value: 'simple',
+ properties: [
+ {
+ type: 'number',
+ name: '',
+ title: 'Slides',
+ },
+ ],
+ },
+ {
+ title: 'Complex',
+ value: 'complex',
+ properties: sliderSizesArray.reduce((acc, size) => {
+ acc.push({type: 'number', name: size, title: size});
+ return acc;
+ }, [] as Array),
+ },
+ ],
+ },
+ {
+ type: 'array',
+ name: 'array',
+ title: 'Array Properties',
+ buttonText: 'Add new',
+ arrayType: 'object',
+ properties: [
+ {
+ type: 'text',
+ name: 'text',
+ title: 'Property',
+ },
+ {
+ type: 'boolean',
+ name: 'boolean',
+ title: 'Property',
+ },
+ ],
+ },
+ ],
+};
+
+export const exampleConfig: BlockConfig = {
+ name: 'Slider Block',
+ inputs: [
+ {
+ type: 'text',
+ name: 'title',
+ title: 'Text Property',
+ },
+ {
+ type: 'boolean',
+ name: '',
+ title: 'Boolean Property',
+ },
+ {
+ type: 'number',
+ name: '',
+ title: 'Number Property',
+ },
+ {
+ type: 'textarea',
+ name: '',
+ title: 'TextArea Property',
+ showIf: `block.description === 'Test'`,
+ },
+ {
+ type: 'select',
+ name: 'select',
+ title: 'Select Property',
+ enum: [
+ {content: 'Option 1', value: 'option-1'},
+ {content: 'Option 2', value: 'option-2'},
+ ],
+ mode: 'single',
+ view: 'select',
+ },
+ {
+ type: 'object',
+ name: 'object_data',
+ title: 'Object Property',
+ properties: [
+ {
+ type: 'text',
+ name: 'text',
+ title: 'Property',
+ },
+ {
+ type: 'boolean',
+ name: 'boolean',
+ title: 'Property',
+ },
+ ],
+ },
+ {
+ type: 'array',
+ name: 'array',
+ title: 'Array Properties',
+ buttonText: 'Add new',
+ arrayType: 'object',
+ properties: [
+ {
+ type: 'text',
+ name: 'text',
+ title: 'Property',
+ },
+ {
+ type: 'boolean',
+ name: 'boolean',
+ title: 'Property',
+ },
+ ],
+ },
+ ],
+};
diff --git a/src/blocks/Slider/index.ts b/src/blocks/Slider/index.ts
new file mode 100644
index 000000000..8e57471e1
--- /dev/null
+++ b/src/blocks/Slider/index.ts
@@ -0,0 +1,48 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import SliderBlock from './Slider';
+import {SliderBlock as SliderBlockSchema} from './schema';
+
+const SliderBlockConfig = {
+ component: SliderBlock,
+ schema: {
+ name: 'Slider Block',
+ group: '@gravity-ui/page-constructor/Card Containers',
+ inputs: generateFromAJV(SliderBlockSchema['slider-block'] as unknown as JSONSchemaType<{}>),
+ default: {
+ dots: true,
+ type: 'slider-block',
+ title: 'Slider Block with Quote Cards',
+ description: 'You can insert any card inside block',
+ slidesToShow: 1,
+ arrows: true,
+ children: [
+ {
+ type: 'quote',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ url: 'https://example.com',
+ author: {
+ firstName: 'Lorem',
+ secondName: 'ipsum',
+ description: 'Lorem ipsum',
+ },
+ },
+ {
+ type: 'quote',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ url: 'https://example.com',
+ author: {
+ firstName: 'Lorem',
+ secondName: 'ipsum',
+ description: 'Lorem ipsum',
+ },
+ },
+ ],
+ },
+ previewImg: 'https://storage.cloud-preprod.yandex.net/qradle-test/card-layout-block.svg',
+ },
+};
+
+export default SliderBlockConfig;
diff --git a/src/blocks/Table/index.ts b/src/blocks/Table/index.ts
new file mode 100644
index 000000000..fdf6abb97
--- /dev/null
+++ b/src/blocks/Table/index.ts
@@ -0,0 +1,33 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import TableBlock from './Table';
+import {TableBlock as TableBlockSchema} from './schema';
+
+const TableBlockConfig = {
+ component: TableBlock,
+ schema: {
+ name: 'Table Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(TableBlockSchema['table-block'] as unknown as JSONSchemaType<{}>),
+ default: {
+ type: 'table-block',
+ title: 'Lorem ipsum dolor sit amet',
+ table: {
+ content: [
+ ['Lorem', 'ipsum 1', 'dolor 2', 'sit 3'],
+ ['Lorem 1', '0', '0', '0'],
+ ['Lorem 2', '0', '0', '1'],
+ ['Lorem 3', '0', '0', '1'],
+ ['Lorem 4', '0', '1', '1'],
+ ['Lorem 5', '1', '1', '1'],
+ ],
+ legend: ['ipsum 1', 'ipsum 2'],
+ justify: ['start', 'center', 'center', 'center'],
+ },
+ },
+ },
+};
+
+export default TableBlockConfig;
diff --git a/src/blocks/Table/schema.ts b/src/blocks/Table/schema.ts
index a6b1ecd2e..30fc6765e 100644
--- a/src/blocks/Table/schema.ts
+++ b/src/blocks/Table/schema.ts
@@ -11,6 +11,7 @@ export const TableBlock = {
contentType: 'text',
},
table: {
+ type: 'object',
additionalProperties: false,
required: ['content'],
properties: {
diff --git a/src/blocks/Tabs/Tabs.tsx b/src/blocks/Tabs/Tabs.tsx
index 889b6215d..347b273d4 100644
--- a/src/blocks/Tabs/Tabs.tsx
+++ b/src/blocks/Tabs/Tabs.tsx
@@ -3,7 +3,7 @@ import * as React from 'react';
import AnimateBlock from '../../components/AnimateBlock/AnimateBlock';
import ButtonTabs, {ButtonTabsItemProps} from '../../components/ButtonTabs/ButtonTabs';
import Title from '../../components/Title/Title';
-import {Col, GridJustifyContent, Row} from '../../grid';
+import {Col, Grid, GridJustifyContent, Row} from '../../grid';
import {TabsBlockProps} from '../../models';
import {block} from '../../utils';
import './Tabs.scss';
@@ -53,42 +53,44 @@ export const TabsBlock = ({
);
return (
- setPlay(true)} animate={animated}>
-
-
-
-
-
-
- {items.map((tabData) => {
- const {tabName} = tabData;
+
+ setPlay(true)} animate={animated}>
+
+
+
+
+
+
+ {items.map((tabData) => {
+ const {tabName} = tabData;
- return (
-
- );
- })}
-
+ return (
+
+ );
+ })}
+
+
);
};
diff --git a/src/blocks/Tabs/index.ts b/src/blocks/Tabs/index.ts
new file mode 100644
index 000000000..4b252984f
--- /dev/null
+++ b/src/blocks/Tabs/index.ts
@@ -0,0 +1,32 @@
+import {JSONSchemaType} from 'ajv';
+
+import {generateFromAJV} from '../../utils/form-generator';
+
+import TabsBlock from './Tabs';
+import {TabsBlock as TabsBlockSchema} from './schema';
+
+const TabsBlockConfig = {
+ component: TabsBlock,
+ schema: {
+ name: 'Tabs Block',
+ group: '@gravity-ui/page-constructor/Blocks',
+ inputs: generateFromAJV(TabsBlockSchema['tabs-block'] as unknown as JSONSchemaType<{}>),
+ default: {
+ title: 'Tabs Block',
+ items: [
+ {
+ tabName: 'First Tab',
+ text: 'First Tab Content',
+ title: 'First Tab Title',
+ },
+ {
+ text: 'Second Tab Content',
+ title: 'Second Tab Title',
+ tabName: 'Second Tab',
+ },
+ ],
+ },
+ },
+};
+
+export default TabsBlockConfig;
diff --git a/src/blocks/TestEditorBlock/TestEditorBlock.tsx b/src/blocks/TestEditorBlock/TestEditorBlock.tsx
new file mode 100644
index 000000000..a9922bd37
--- /dev/null
+++ b/src/blocks/TestEditorBlock/TestEditorBlock.tsx
@@ -0,0 +1,13 @@
+import * as React from 'react';
+
+import {block} from '../../utils';
+
+const b = block('test-editor-block');
+
+export interface TestEditorBlockProps extends React.PropsWithChildren {}
+
+export const TestEditorBlock = (props: TestEditorBlockProps) => {
+ return {JSON.stringify(props, null, 2)}
;
+};
+
+export default TestEditorBlock;
diff --git a/src/blocks/TestEditorBlock/form.ts b/src/blocks/TestEditorBlock/form.ts
new file mode 100644
index 000000000..21aca45d5
--- /dev/null
+++ b/src/blocks/TestEditorBlock/form.ts
@@ -0,0 +1,114 @@
+import {
+ ArrayObjectInput,
+ ArrayTextInput,
+ BooleanInput,
+ NumberInput,
+ ObjectInput,
+ OneOfInput,
+ SelectMultipleInput,
+ SelectSingleInput,
+ TextAreaInput,
+ TextInput,
+} from '../../form-generator';
+
+const textInput: TextInput = {
+ type: 'text',
+ name: 'text',
+ title: 'Text Input',
+};
+
+const textAreaInput: TextAreaInput = {
+ type: 'textarea',
+ name: 'textarea',
+ title: 'TextArea Input',
+};
+
+const booleanInput: BooleanInput = {
+ type: 'boolean',
+ name: 'boolean',
+ title: 'Boolean Input',
+};
+
+const numberInput: NumberInput = {
+ type: 'number',
+ name: 'number',
+ title: 'Number Input',
+};
+
+const selectInput: SelectSingleInput = {
+ type: 'select',
+ name: 'selectSingle',
+ title: 'Select Single Input',
+ mode: 'single',
+ view: 'select',
+ enum: [
+ {value: 'id_1', content: 'Option 1'},
+ {value: 'id_2', content: 'Option 2'},
+ ],
+};
+
+const radioButtonsViewSingleInput: SelectSingleInput = {
+ ...selectInput,
+ name: 'radioButtons',
+ title: 'Radio Button Input',
+
+ view: 'radiobutton',
+};
+
+// @ts-ignore
+const selectMultipleModeInput: SelectMultipleInput = {
+ ...selectInput,
+ name: 'selectMultiple',
+ title: 'Select Multiple Input',
+
+ mode: 'multiple',
+};
+
+const objectInput: ObjectInput = {
+ type: 'object',
+ name: 'object',
+ title: 'Object Input',
+ properties: [textInput, textAreaInput, selectInput],
+};
+
+const arrayTextInput: ArrayTextInput = {
+ type: 'array',
+ name: 'arrayText',
+ title: 'Array Text Input',
+ buttonText: 'Add Array Item',
+ arrayType: 'text',
+};
+
+const arrayObjectInput: ArrayObjectInput = {
+ type: 'array',
+ name: 'arrayObject',
+ title: 'Array Object Input',
+ buttonText: 'Add Array Item',
+ arrayType: 'object',
+ properties: [textInput, textAreaInput, selectInput],
+};
+
+const oneOfInput: OneOfInput = {
+ type: 'oneOf',
+ name: 'oneOf',
+ key: 'oneOfKey',
+ title: 'Array Text Input',
+ options: [
+ {value: 'text', title: 'Text', properties: [textInput]},
+ {value: 'textarea', title: 'TextArea', properties: [textAreaInput]},
+ ],
+};
+
+export default [
+ textInput,
+ textAreaInput,
+ booleanInput,
+ numberInput,
+ selectInput,
+ radioButtonsViewSingleInput,
+ selectMultipleModeInput,
+ objectInput,
+ arrayTextInput,
+ arrayObjectInput,
+ oneOfInput,
+];
diff --git a/src/blocks/TestEditorBlock/index.ts b/src/blocks/TestEditorBlock/index.ts
new file mode 100644
index 000000000..b999fdf7e
--- /dev/null
+++ b/src/blocks/TestEditorBlock/index.ts
@@ -0,0 +1,16 @@
+import TestEditorBlock from './TestEditorBlock';
+import testEditorBlockInputs from './form';
+
+const TestEditorBlockConfig = {
+ component: TestEditorBlock,
+ schema: {
+ name: 'Test Editor Block',
+ inputs: testEditorBlockInputs,
+ },
+};
+
+export const TestEditorBlockSchema = {
+ ['test-editor-block']: {},
+};
+
+export default TestEditorBlockConfig;
diff --git a/src/common/postMessage.ts b/src/common/postMessage.ts
new file mode 100644
index 000000000..54fe90f93
--- /dev/null
+++ b/src/common/postMessage.ts
@@ -0,0 +1,44 @@
+import * as React from 'react';
+
+import {ActionMessageTypes, EventMessageTypes, PostMessageAPIMessage} from './types';
+
+export function requestActionPostMessage(
+ action: K,
+ data: ActionMessageTypes[K],
+ destinationElement: Window,
+) {
+ const message = {action, data} as PostMessageAPIMessage;
+ destinationElement.postMessage(message);
+}
+
+export function listenPostMessageEvents(
+ action: K,
+ callback: (data: EventMessageTypes[K]) => void,
+) {
+ const onMessage = (e: MessageEvent) => {
+ const message = e.data as PostMessageAPIMessage;
+
+ if ('action' in message && message.action === action) {
+ return callback(message.data);
+ }
+
+ return undefined;
+ };
+
+ window.addEventListener('message', onMessage);
+
+ return () => {
+ window.removeEventListener('message', onMessage);
+ };
+}
+
+export function usePostMessageAPIListener(
+ action: K,
+ callback: (data: EventMessageTypes[K]) => void,
+ deps: unknown[] = [],
+) {
+ React.useEffect(() => {
+ return listenPostMessageEvents(action, callback);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [...deps]);
+}
diff --git a/src/common/store.ts b/src/common/store.ts
new file mode 100644
index 000000000..b9bb8a3e4
--- /dev/null
+++ b/src/common/store.ts
@@ -0,0 +1,44 @@
+import _ from 'lodash';
+
+import {PageContentWithNavigation} from '../models';
+
+import {ConfigInput} from '../form-generator';
+import {ItemConfig} from './types';
+import {initializeStore} from './utils';
+
+export interface EditorState {
+ height?: number;
+ deviceWidth?: string;
+ zoom: number;
+
+ manipulateOverlayMode: 'insert' | 'reorder' | false;
+ selectedBlock: number[] | null;
+ initialized: boolean;
+ isPreviewMode: boolean;
+
+ content: PageContentWithNavigation;
+ blocks: Array;
+ subBlocks: Array;
+ global: Array;
+
+ preInsertBlockType: string | null;
+ preReorderBlockPath: number[] | null;
+}
+
+export const initialStore: EditorState = {
+ height: 100,
+ deviceWidth: '100%',
+ zoom: 100,
+ manipulateOverlayMode: false,
+ selectedBlock: null,
+ initialized: false,
+ isPreviewMode: false,
+ content: {blocks: []},
+ blocks: [],
+ subBlocks: [],
+ global: [],
+ preInsertBlockType: null,
+ preReorderBlockPath: null,
+};
+
+export const createPCEditorStore = initializeStore(initialStore, () => ({}));
diff --git a/src/common/types/actions.ts b/src/common/types/actions.ts
new file mode 100644
index 000000000..1fbf0fdc5
--- /dev/null
+++ b/src/common/types/actions.ts
@@ -0,0 +1,21 @@
+import {PageContentWithNavigation} from '../../models';
+import {EditorState} from '../store';
+
+export type MessageTypes = EventMessageTypes & ActionMessageTypes;
+
+export type EventMessageTypes = {
+ ON_INIT: {height: number};
+ ON_RESIZE: {height: number};
+ ON_MOUSE_UP: {path?: number[]; rect?: DOMRect; position?: string};
+ ON_MOUSE_MOVE: {x: number; y: number};
+ ON_HOVER_BLOCK: {rect?: DOMRect; position?: string};
+ ON_CLICK_BLOCK: {path: number[]};
+ ON_UPDATE_BLOCK_SELECTION: {rect: DOMRect};
+ ON_SUPPORTED_BLOCKS: Pick;
+ ON_INITIAL_CONTENT: PageContentWithNavigation;
+};
+
+export type ActionMessageTypes = {
+ GET_SUPPORTED_BLOCKS: {};
+ GET_INITIAL_CONTENT: {};
+};
diff --git a/src/common/types/common.ts b/src/common/types/common.ts
new file mode 100644
index 000000000..f7051d27c
--- /dev/null
+++ b/src/common/types/common.ts
@@ -0,0 +1,6 @@
+import {BlockConfig} from '../../form-generator/types';
+
+export interface ItemConfig {
+ type: string;
+ schema: BlockConfig;
+}
diff --git a/src/common/types/index.ts b/src/common/types/index.ts
new file mode 100644
index 000000000..cfb7cdbf1
--- /dev/null
+++ b/src/common/types/index.ts
@@ -0,0 +1,3 @@
+export * from './actions';
+export * from './common';
+export * from './messages';
diff --git a/src/common/types/messages.ts b/src/common/types/messages.ts
new file mode 100644
index 000000000..aa9ec2a62
--- /dev/null
+++ b/src/common/types/messages.ts
@@ -0,0 +1,12 @@
+import {EditorState} from '../store';
+
+import {MessageTypes} from './actions';
+
+export type PostMessageAPIMessage = {
+ action: K;
+ data: MessageTypes[K];
+};
+
+export type StoreSyncMessage = {
+ state: EditorState;
+};
diff --git a/src/common/utils.ts b/src/common/utils.ts
new file mode 100644
index 000000000..666e53573
--- /dev/null
+++ b/src/common/utils.ts
@@ -0,0 +1,35 @@
+import {StoreApi, create} from 'zustand';
+import {devtools, subscribeWithSelector} from 'zustand/middleware';
+
+export function initializeStore(
+ initialState: State,
+ methods: (
+ set: StoreApi['setState'],
+ get: StoreApi['getState'],
+ ) => Methods,
+) {
+ return (overrideInitialState?: Partial) => {
+ return create<
+ State & Methods,
+ [
+ ['zustand/subscribeWithSelector', State & Methods],
+ ['zustand/devtools', never],
+ ['zustand/persist', State & Methods],
+ ]
+ >(
+ subscribeWithSelector(
+ devtools((set, get) => {
+ return {
+ ...initialState,
+ ...overrideInitialState,
+ ...methods(set, get),
+ };
+ }),
+ ),
+ );
+ };
+}
+
+export const removeFn = (object: object) => {
+ return JSON.parse(JSON.stringify(object));
+};
diff --git a/src/components/BlockBase/BlockBase.scss b/src/components/BlockBase/BlockBase.scss
index 01221dfc3..88c2fc656 100644
--- a/src/components/BlockBase/BlockBase.scss
+++ b/src/components/BlockBase/BlockBase.scss
@@ -14,12 +14,12 @@ $block: '.#{$ns}block-base';
@include add-specificity(&) {
@media only screen and (max-width: map-get($gridBreakpoints, 'sm')) {
- margin-top: $indentM;
+ padding-top: $indentM;
padding-bottom: $indentM;
- &:first-child {
- margin-top: var(--pc-first-block-mobile-indent, #{$indentXL});
- }
+ //&:first-child {
+ // padding-top: var(--pc-first-block-mobile-indent, #{$indentXL});
+ //}
}
}
diff --git a/src/components/Image/dynamic-form.ts b/src/components/Image/dynamic-form.ts
new file mode 100644
index 000000000..4871a5d26
--- /dev/null
+++ b/src/components/Image/dynamic-form.ts
@@ -0,0 +1,87 @@
+import _ from 'lodash';
+
+import {ConfigInput} from '../../form-generator';
+
+const devices = ['desktop', 'tablet', 'mobile'];
+
+const imageBaseInputs: ConfigInput[] = [
+ {
+ type: 'text',
+ name: 'alt',
+ title: 'Alternative',
+ },
+ {
+ type: 'boolean',
+ name: 'disableCompress',
+ title: 'Disable Compress',
+ },
+];
+
+const imageStyleInputs: ConfigInput[] = [
+ {
+ type: 'text',
+ name: 'style.backgroundColor',
+ title: 'Background Color',
+ },
+ {
+ type: 'text',
+ name: 'style.height',
+ title: 'Height',
+ },
+ {
+ type: 'text',
+ name: 'style.width',
+ title: 'Width',
+ },
+ {
+ type: 'text',
+ name: 'style.color',
+ title: 'Color',
+ },
+];
+
+const devicesInputs: ConfigInput[] = devices.map((device) => ({
+ type: 'text',
+ title: _.capitalize(device),
+ name: `${device}`,
+}));
+
+export const imageInputs: ConfigInput[] = [
+ {
+ type: 'oneOf',
+ name: '',
+ key: 'imageType',
+ title: 'Image Type',
+ options: [
+ {
+ title: 'Simple',
+ value: 'simple',
+ properties: [
+ {
+ type: 'text',
+ name: '', // image props
+ title: 'Image URL',
+ },
+ ],
+ },
+ {
+ title: 'Complex',
+ value: 'complex',
+ properties: [
+ {
+ type: 'text',
+ name: 'src',
+ title: 'Source',
+ },
+ ...imageStyleInputs,
+ ...imageBaseInputs,
+ ],
+ },
+ {
+ title: 'Device Based',
+ value: 'deviseBased',
+ properties: [...devicesInputs, ...imageBaseInputs],
+ },
+ ],
+ },
+];
diff --git a/src/components/editor/ItemWrap/ItemWrap.scss b/src/components/editor/ItemWrap/ItemWrap.scss
new file mode 100644
index 000000000..40bbfad31
--- /dev/null
+++ b/src/components/editor/ItemWrap/ItemWrap.scss
@@ -0,0 +1,12 @@
+@import '../../../../styles/mixins.scss';
+@import '../../../../styles/variables.scss';
+
+$block: '.#{$ns}item-wrap';
+
+#{$block} {
+ height: 100%;
+}
+
+.pc-page-constructor_with-editor #{$block} {
+ cursor: pointer;
+}
diff --git a/src/components/editor/ItemWrap/ItemWrap.tsx b/src/components/editor/ItemWrap/ItemWrap.tsx
new file mode 100644
index 000000000..cca5b199c
--- /dev/null
+++ b/src/components/editor/ItemWrap/ItemWrap.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react';
+
+import {usePCEditorItemWrap} from '../../../hooks/usePCEditorItemWrap';
+import {block} from '../../../utils';
+
+import './ItemWrap.scss';
+
+const b = block('item-wrap');
+
+export interface ItemWrapProps extends React.PropsWithChildren {
+ index: number;
+}
+
+const ItemWrap = ({index, children}: ItemWrapProps) => {
+ const {blockRef, adminBlockMouseEvents} = usePCEditorItemWrap(index);
+ return (
+
+ {children}
+
+ );
+};
+
+export default ItemWrap;
diff --git a/src/constructor-items.ts b/src/constructor-items.ts
index 6416046ad..2bd6b2123 100644
--- a/src/constructor-items.ts
+++ b/src/constructor-items.ts
@@ -1,3 +1,7 @@
+import * as React from 'react';
+
+import {BlockConfig} from './form-generator';
+
import {
BannerBlock,
CardLayoutBlock,
@@ -20,14 +24,30 @@ import {
TableBlock,
TabsBlock,
} from './blocks';
+import BannerBlockConfig from './blocks/Banner';
+import CardLayoutBlockConfig from './blocks/CardLayout';
+import CompaniesBlockConfig from './blocks/Companies';
+import ContentLayoutBlockConfig from './blocks/ContentLayout';
+import ExtendedFeaturesBlockConfig from './blocks/ExtendedFeatures';
+// import FilterBlockConfig from './blocks/FilterBlock';
+import FormBlockConfig from './blocks/Form';
+import HeaderBlockConfig from './blocks/Header';
+import HeaderSliderBlockConfig from './blocks/HeaderSlider';
+import IconsBlockConfig from './blocks/Icons';
+import InfoBlockConfig from './blocks/Info';
+import MapBlockConfig from './blocks/Map';
+import MediaBlockConfig from './blocks/Media';
+import PromoFeaturesBlockConfig from './blocks/PromoFeaturesBlock';
+import QuestionsBlockConfig from './blocks/Questions';
+import ShareBlockConfig from './blocks/Share';
+import SliderBlockConfig from './blocks/Slider';
+import TableBlockConfig from './blocks/Table';
+import TabsBlockConfig from './blocks/Tabs';
+import TestEditorBlockConfig from './blocks/TestEditorBlock';
+import TestEditorBlock from './blocks/TestEditorBlock/TestEditorBlock';
import {SliderNewBlock} from './blocks/unstable';
import {BlockType, NavigationItemType, SubBlockType} from './models';
-import {
- GithubButton,
- NavigationButton,
- NavigationDropdown,
- NavigationLink,
-} from './navigation/components/NavigationItem';
+import {GithubButton, NavigationButton, NavigationDropdown, NavigationLink} from './navigation';
import SocialIcon from './navigation/components/SocialIcon/SocialIcon';
import {
BackgroundCard,
@@ -42,7 +62,21 @@ import {
PriceDetailed,
Quote,
} from './sub-blocks';
+import BackgroundCardConfig from './sub-blocks/BackgroundCard';
+import BannerCardConfig from './sub-blocks/BannerCard';
+import BasicCardConfig from './sub-blocks/BasicCard';
+import ContentConfig from './sub-blocks/Content';
+import DividerConfig from './sub-blocks/Divider';
+import ImageCardConfig from './sub-blocks/ImageCard';
+import LayoutItemConfig from './sub-blocks/LayoutItem';
+import MediaCardConfig from './sub-blocks/MediaCard';
+import PriceCardConfig from './sub-blocks/PriceCard';
+import QuoteConfig from './sub-blocks/Quote';
+/**
+ * TODO: remove it
+ * @deprecated use blockDataMap
+ **/
export const blockMap = {
[BlockType.SliderBlock]: SliderBlock,
[BlockType.ExtendedFeaturesBlock]: ExtendedFeaturesBlock,
@@ -66,8 +100,13 @@ export const blockMap = {
[BlockType.FormBlock]: FormBlock,
// unstable
[BlockType.SliderNewBlock]: SliderNewBlock,
+ [BlockType.TestEditorBlock]: TestEditorBlock,
};
+/**
+ * TODO: remove it
+ * @deprecated use blockDataMap
+ **/
export const subBlockMap = {
[SubBlockType.Divider]: Divider,
[SubBlockType.PriceDetailed]: PriceDetailed,
@@ -89,3 +128,45 @@ export const navItemMap = {
[NavigationItemType.Link]: NavigationLink,
[NavigationItemType.GithubButton]: GithubButton,
};
+
+export interface BlockData {
+ // TODO: remove any
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ component: React.ComponentType;
+ schema: BlockConfig;
+}
+
+export const blockDataMap: Record = {
+ [BlockType.ExtendedFeaturesBlock]: ExtendedFeaturesBlockConfig,
+ [BlockType.PromoFeaturesBlock]: PromoFeaturesBlockConfig,
+ [BlockType.QuestionsBlock]: QuestionsBlockConfig,
+ [BlockType.BannerBlock]: BannerBlockConfig,
+ [BlockType.CompaniesBlock]: CompaniesBlockConfig,
+ [BlockType.MediaBlock]: MediaBlockConfig,
+ [BlockType.InfoBlock]: InfoBlockConfig,
+ [BlockType.TableBlock]: TableBlockConfig,
+ [BlockType.TabsBlock]: TabsBlockConfig,
+ [BlockType.HeaderBlock]: HeaderBlockConfig,
+ [BlockType.IconsBlock]: IconsBlockConfig,
+ [BlockType.HeaderSliderBlock]: HeaderSliderBlockConfig,
+ [BlockType.CardLayoutBlock]: CardLayoutBlockConfig,
+ [BlockType.ContentLayoutBlock]: ContentLayoutBlockConfig,
+ [BlockType.ShareBlock]: ShareBlockConfig,
+ [BlockType.MapBlock]: MapBlockConfig,
+ // TODO: fix items prop for editor compatibility
+ // [BlockType.FilterBlock]: FilterBlockConfig,
+ [BlockType.FormBlock]: FormBlockConfig,
+ [BlockType.TestEditorBlock]: TestEditorBlockConfig,
+ [BlockType.SliderBlock]: SliderBlockConfig,
+
+ [SubBlockType.Divider]: DividerConfig,
+ [SubBlockType.MediaCard]: MediaCardConfig,
+ [SubBlockType.BannerCard]: BannerCardConfig,
+ [SubBlockType.LayoutItem]: LayoutItemConfig,
+ [SubBlockType.BackgroundCard]: BackgroundCardConfig,
+ [SubBlockType.BasicCard]: BasicCardConfig,
+ [SubBlockType.Content]: ContentConfig,
+ [SubBlockType.Quote]: QuoteConfig,
+ [SubBlockType.PriceCard]: PriceCardConfig,
+ [SubBlockType.ImageCard]: ImageCardConfig,
+};
diff --git a/src/containers/PageConstructor/PageConstructor.tsx b/src/containers/PageConstructor/PageConstructor.tsx
index ccb8a8a24..988fd5754 100644
--- a/src/containers/PageConstructor/PageConstructor.tsx
+++ b/src/containers/PageConstructor/PageConstructor.tsx
@@ -1,6 +1,7 @@
import * as React from 'react';
-import '@diplodoc/transform/dist/js/yfm.js';
+import '@diplodoc/transform/dist/js/yfm';
+import {ThemeProvider} from '@gravity-ui/uikit';
import BackgroundMedia from '../../components/BackgroundMedia/BackgroundMedia';
import BrandFooter from '../../components/BrandFooter/BrandFooter';
@@ -10,9 +11,9 @@ import {AnimateContext} from '../../context/animateContext';
import {InnerContext} from '../../context/innerContext';
import {ProjectSettingsContext} from '../../context/projectSettingsContext';
import {useTheme} from '../../context/theme';
-import {Grid} from '../../grid';
+import {usePCEditorInitializeEvents} from '../../hooks/usePCEditorInitializeEvents';
+import {usePCEditorStore} from '../../hooks/usePCEditorStore';
import {
- BlockType,
BlockTypes,
CustomConfig,
CustomItems,
@@ -20,21 +21,14 @@ import {
NavigationData,
NavigationItemTypes,
PageContent,
+ PageContentWithNavigation,
ShouldRenderBlock,
SubBlockTypes,
} from '../../models';
import Layout from '../../navigation/containers/Layout/Layout';
-import {
- block as cnBlock,
- getCustomItems,
- getCustomTypes,
- getHeaderBlock,
- getOrderedBlocks,
- getThemedValue,
-} from '../../utils';
-
-import {ConstructorBlocks} from './components/ConstructorBlocks';
-import {ConstructorHeader} from './components/ConstructorItem';
+import {block as cnBlock, getCustomItems, getCustomTypes, getThemedValue} from '../../utils';
+
+import {ConstructorBlocks} from './components';
import {ConstructorRow} from './components/ConstructorRow';
import './PageConstructor.scss';
@@ -68,6 +62,19 @@ export const Constructor = (props: PageConstructorProps) => {
microdata,
} = props;
+ const [content, setContent] = React.useState({
+ blocks,
+ background,
+ navigation,
+ });
+
+ const theme = useTheme();
+
+ const store = usePCEditorStore();
+ const {initialized, isPreviewMode} = store;
+
+ usePCEditorInitializeEvents({initialContent: {blocks, background, navigation}, setContent});
+
const {context} = React.useMemo(
() => ({
context: {
@@ -98,31 +105,42 @@ export const Constructor = (props: PageConstructorProps) => {
[custom, shouldRenderBlock, microdata],
);
- const theme = useTheme();
+ const restBlocks = content.blocks;
+ const themedBackground = getThemedValue(content.background, theme);
+
+ // disable click events
+ React.useEffect(() => {
+ if (!initialized || isPreviewMode) {
+ return;
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const handler: React.EventHandler = (e) => {
+ e?.preventDefault();
+ const blockElement = e.target.closest('[data-editor-item]');
+ blockElement.click(e);
+ };
+ document.body.addEventListener('click', handler);
- const header = getHeaderBlock(blocks, context.headerBlockTypes);
- const restBlocks = getOrderedBlocks(blocks, context.headerBlockTypes);
- const themedBackground = getThemedValue(background, theme);
+ // eslint-disable-next-line consistent-return
+ return () => {
+ document.body.removeEventListener('click', handler);
+ };
+ }, [initialized, isPreviewMode]);
return (
-
+
{themedBackground && (
)}
-
+
{renderMenu && renderMenu()}
- {header && (
-
+ {restBlocks && (
+
+
+
)}
-
- {restBlocks && (
-
-
-
- )}
-
{isBranded && }
@@ -136,8 +154,10 @@ export const PageConstructor = (props: PageConstructorProps) => {
const {content: {animated = isAnimationEnabled} = {}, ...rest} = props;
return (
-
-
-
+
+
+
+
+
);
};
diff --git a/src/containers/PageConstructor/Provider.tsx b/src/containers/PageConstructor/Provider.tsx
index 3df2de962..389c852d2 100644
--- a/src/containers/PageConstructor/Provider.tsx
+++ b/src/containers/PageConstructor/Provider.tsx
@@ -2,6 +2,7 @@ import * as React from 'react';
import {DEFAULT_THEME} from '../../components/constants';
import {AnalyticsContext, AnalyticsContextProps} from '../../context/analyticsContext';
+import {PCEditorStoreProvider} from '../../context/editorStoreContext';
import {
DEFAULT_FORMS_CONTEXT_VALUE,
FormsContext,
@@ -62,6 +63,7 @@ export const PageConstructorProvider = (
,
,
,
+ ,
].reduceRight((prev, provider) => React.cloneElement(provider, {}, prev), children);
/* eslint-enable react/jsx-key */
diff --git a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.scss b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.scss
index b6c7cbc86..3f49e0086 100644
--- a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.scss
+++ b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.scss
@@ -6,3 +6,7 @@ $block: '.#{$ns}constructor-block';
#{$block} {
@include indents(&);
}
+
+.pc-page-constructor_with-editor #{$block} {
+ cursor: pointer;
+}
diff --git a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx
index b41b5bc18..469f8ab48 100644
--- a/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx
+++ b/src/containers/PageConstructor/components/ConstructorBlock/ConstructorBlock.tsx
@@ -4,6 +4,7 @@ import pick from 'lodash/pick';
import BlockBase from '../../../../components/BlockBase/BlockBase';
import {BlockDecoration} from '../../../../customization/BlockDecoration';
+import {usePCEditorItemWrap} from '../../../../hooks/usePCEditorItemWrap';
import {BlockDecorationProps, ConstructorBlock as ConstructorBlockType} from '../../../../models';
import {block} from '../../../../utils';
@@ -20,6 +21,8 @@ export const ConstructorBlock = ({
data,
children,
}: React.PropsWithChildren) => {
+ const {blockRef, adminBlockMouseEvents} = usePCEditorItemWrap(index);
+
const {type} = data;
const blockBaseProps = React.useMemo(
() => pick(data, ['anchor', 'visible', 'resetPaddings', 'indent']),
@@ -27,10 +30,12 @@ export const ConstructorBlock = ({
);
return (
-
-
- {children}
-
-
+
+
+
+ {children}
+
+
+
);
};
diff --git a/src/containers/PageConstructor/components/ConstructorBlocks/ConstructorBlocks.tsx b/src/containers/PageConstructor/components/ConstructorBlocks/ConstructorBlocks.tsx
index 9903e2f88..2c9f43204 100644
--- a/src/containers/PageConstructor/components/ConstructorBlocks/ConstructorBlocks.tsx
+++ b/src/containers/PageConstructor/components/ConstructorBlocks/ConstructorBlocks.tsx
@@ -20,10 +20,14 @@ export interface ConstructorBlocksProps {
}
export const ConstructorBlocks: React.FC = ({items}) => {
- const {blockTypes, loadables, itemMap, shouldRenderBlock} = React.useContext(InnerContext);
+ const {blockTypes, subBlockTypes, loadables, itemMap, shouldRenderBlock} =
+ React.useContext(InnerContext);
+
+ const allBlocks = [...blockTypes, ...subBlockTypes];
const renderer = (
parentId = '',
+ withoutConstructorBlockWrapper = false,
item: ConstructorBlockType,
index: number,
): React.ReactElement | null => {
@@ -63,17 +67,17 @@ export const ConstructorBlocks: React.FC = ({items}) =>
} else {
let children;
if ('children' in item && item.children) {
- children = (item.children as SubBlock[]).map(renderer.bind(null, blockId));
+ children = (item.children as SubBlock[]).map(renderer.bind(null, blockId, true));
}
itemElement = (
-
+
{children}
);
}
- return blockTypes.includes(item.type) ? (
+ return allBlocks.includes(item.type) && !withoutConstructorBlockWrapper ? (
//TODO: replace ConstructorBlock (and delete it) with BlockBase when all
// components relying on constructor inner structure like Slider or blog-constructor will be refactored
@@ -84,5 +88,5 @@ export const ConstructorBlocks: React.FC = ({items}) =>
);
};
- return {items.map(renderer.bind(null, ''))} ;
+ return {items.map(renderer.bind(null, '', false))} ;
};
diff --git a/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx b/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx
index 182f20641..7b80cda6d 100644
--- a/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx
+++ b/src/containers/PageConstructor/components/ConstructorItem/ConstructorItem.tsx
@@ -7,7 +7,7 @@ import {BlockType, ConstructorBlock} from '../../../../models';
export interface ConstructorItemProps {
data: ConstructorBlock;
- blockKey: string;
+ blockKey: number;
}
export const ConstructorItem = ({
@@ -16,6 +16,7 @@ export const ConstructorItem = ({
children,
}: React.PropsWithChildren) => {
const {itemMap} = React.useContext(InnerContext);
+ const parentId = React.useContext(BlockIdContext);
const {type, ...rest} = data;
const Component = itemMap[type] as React.ComponentType<
@@ -23,7 +24,7 @@ export const ConstructorItem = ({
>;
return (
-
+
{children}
);
diff --git a/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx b/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx
index 4488dd6f2..21ca4b234 100644
--- a/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx
+++ b/src/containers/PageConstructor/components/ConstructorLoadable/ConstructorLoadable.tsx
@@ -18,9 +18,10 @@ export const ConstructorLoadable = (props: ConstructorLoadableProps) => {
const Component = itemMap[type] as React.Component<
React.ComponentProps<(typeof itemMap)[typeof type]>
>;
+ const parentId = React.useContext(BlockIdContext);
return (
-
+
) =>
- children ? (
-
- {children}
-
- ) : null;
+ children ? {children}
: null;
diff --git a/src/context/blockIdContext/blockIdContext.ts b/src/context/blockIdContext/blockIdContext.ts
index 289ed964d..bc82a8b72 100644
--- a/src/context/blockIdContext/blockIdContext.ts
+++ b/src/context/blockIdContext/blockIdContext.ts
@@ -1,5 +1,5 @@
import * as React from 'react';
-export type BlockIdContextProp = string;
+export type BlockIdContextProp = number[];
-export const BlockIdContext = React.createContext('');
+export const BlockIdContext = React.createContext([]);
diff --git a/src/context/editorStoreContext/PCEditorStoreContext.tsx b/src/context/editorStoreContext/PCEditorStoreContext.tsx
new file mode 100644
index 000000000..1b9e53899
--- /dev/null
+++ b/src/context/editorStoreContext/PCEditorStoreContext.tsx
@@ -0,0 +1,13 @@
+import * as React from 'react';
+
+import {StoreApi} from 'zustand';
+
+import {EditorState, createPCEditorStore} from '../../common/store';
+
+export interface PCEditorStoreContextProps {
+ state: StoreApi;
+}
+
+export const PCEditorStoreContext = React.createContext({
+ state: createPCEditorStore(),
+});
diff --git a/src/context/editorStoreContext/PCEditorStoreProvider.tsx b/src/context/editorStoreContext/PCEditorStoreProvider.tsx
new file mode 100644
index 000000000..b3d851565
--- /dev/null
+++ b/src/context/editorStoreContext/PCEditorStoreProvider.tsx
@@ -0,0 +1,47 @@
+import * as React from 'react';
+
+import {StoreApi} from 'zustand';
+
+import {EditorState, createPCEditorStore} from '../../common/store';
+import {StoreSyncMessage} from '../../common/types';
+
+import {PCEditorStoreContext} from './PCEditorStoreContext';
+
+interface PCEditorStoreProviderProps extends React.PropsWithChildren {}
+
+export const PCEditorStoreProvider = ({children}: PCEditorStoreProviderProps) => {
+ const storeRef = React.useRef>();
+
+ const syncStore = React.useCallback((message: StoreSyncMessage) => {
+ if (storeRef.current && message.state) {
+ storeRef.current.setState(message.state);
+ }
+ }, []);
+
+ React.useEffect(() => {
+ const onMessage = (e: MessageEvent) => {
+ const message = e.data as StoreSyncMessage;
+ syncStore(message);
+ };
+
+ window.addEventListener('message', onMessage);
+
+ return () => {
+ window.removeEventListener('message', onMessage);
+ };
+ }, [syncStore]);
+
+ if (!storeRef.current) {
+ storeRef.current = createPCEditorStore();
+ }
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/context/editorStoreContext/index.ts b/src/context/editorStoreContext/index.ts
new file mode 100644
index 000000000..2cb38452e
--- /dev/null
+++ b/src/context/editorStoreContext/index.ts
@@ -0,0 +1,2 @@
+export * from './PCEditorStoreContext';
+export * from './PCEditorStoreProvider';
diff --git a/src/editor-v2/components/BlockCard/BlockCard.scss b/src/editor-v2/components/BlockCard/BlockCard.scss
new file mode 100644
index 000000000..a55639b92
--- /dev/null
+++ b/src/editor-v2/components/BlockCard/BlockCard.scss
@@ -0,0 +1,39 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}block-card';
+
+#{$block} {
+ padding: 8px;
+ cursor: grab;
+ user-select: none;
+ margin-bottom: 8px;
+
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ text-align: center;
+ gap: 4px;
+ max-width: 300px;
+
+ &:active {
+ background-color: var(--g-color-base-generic-hover);
+ cursor: grabbing;
+ }
+
+ &__image {
+ &-img {
+ pointer-events: none;
+ }
+ }
+
+ &__name {
+ justify-self: flex-end;
+ color: var(--g-color-text-secondary);
+ }
+
+ &__icon {
+ color: var(--g-color-text-complementary);
+ }
+}
diff --git a/src/editor-v2/components/BlockCard/BlockCard.tsx b/src/editor-v2/components/BlockCard/BlockCard.tsx
new file mode 100644
index 000000000..4e1dd4896
--- /dev/null
+++ b/src/editor-v2/components/BlockCard/BlockCard.tsx
@@ -0,0 +1,38 @@
+import {SquareBars} from '@gravity-ui/icons';
+import {Card, Icon} from '@gravity-ui/uikit';
+import * as React from 'react';
+
+import {editorCn} from '../../utils/cn';
+
+import './BlockCard.scss';
+
+const b = editorCn('block-card');
+
+export interface BlockCardProps {
+ className?: string;
+ type: string;
+ name: string;
+ previewImg?: string;
+ onMouseDown: (type: string) => void;
+}
+
+const BlockCard = ({className, type, name, previewImg, onMouseDown}: BlockCardProps) => {
+ const handleMouseDown = React.useCallback(() => {
+ onMouseDown(type);
+ }, [onMouseDown, type]);
+
+ return (
+
+
+ {previewImg ? (
+
+ ) : (
+
+ )}
+
+ {name}
+
+ );
+};
+
+export default BlockCard;
diff --git a/src/editor-v2/components/MessageCard/MessageCard.scss b/src/editor-v2/components/MessageCard/MessageCard.scss
new file mode 100644
index 000000000..a3c19f548
--- /dev/null
+++ b/src/editor-v2/components/MessageCard/MessageCard.scss
@@ -0,0 +1,68 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}message-card';
+
+#{$block} {
+ padding: 16px;
+ border-radius: 8px;
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+
+ &__icon {
+ flex-shrink: 0;
+ margin-top: 2px;
+ }
+
+ &__content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ }
+
+ &__title {
+ margin: 0;
+ }
+
+ &__description {
+ margin: 0;
+ }
+
+ &_theme_success {
+ border: 1px solid var(--g-color-text-positive);
+ background-color: var(--g-color-base-positive-light);
+
+ #{$block}__icon {
+ color: var(--g-color-text-positive);
+ }
+ }
+
+ &_theme_error {
+ border: 1px solid var(--g-color-text-danger);
+ background-color: var(--g-color-base-danger-light);
+
+ #{$block}__icon {
+ color: var(--g-color-text-danger);
+ }
+ }
+
+ &_theme_warning {
+ border: 1px solid var(--g-color-text-warning);
+ background-color: var(--g-color-base-warning-light);
+
+ #{$block}__icon {
+ color: var(--g-color-text-warning);
+ }
+ }
+
+ &_theme_info {
+ border: 1px solid var(--g-color-text-info);
+ background-color: var(--g-color-base-info-light);
+
+ #{$block}__icon {
+ color: var(--g-color-text-info);
+ }
+ }
+}
diff --git a/src/editor-v2/components/MessageCard/MessageCard.tsx b/src/editor-v2/components/MessageCard/MessageCard.tsx
new file mode 100644
index 000000000..1ae773ab7
--- /dev/null
+++ b/src/editor-v2/components/MessageCard/MessageCard.tsx
@@ -0,0 +1,43 @@
+import * as React from 'react';
+import {Icon, IconData, Text} from '@gravity-ui/uikit';
+import {Check, CircleInfo, TriangleExclamation, Xmark} from '@gravity-ui/icons';
+
+import {editorCn} from '../../utils/cn';
+
+const b = editorCn('message-card');
+
+import './MessageCard.scss';
+
+export type MessageTheme = 'success' | 'error' | 'warning' | 'info';
+
+const DEFAULT_ICONS: Record = {
+ success: Check,
+ error: Xmark,
+ warning: TriangleExclamation,
+ info: CircleInfo,
+};
+
+export interface MessageCardProps {
+ title: string;
+ description: string;
+ theme: MessageTheme;
+ icon?: IconData;
+}
+
+export const MessageCard: React.FC = ({title, description, theme, icon}) => {
+ const IconComponent = icon || DEFAULT_ICONS[theme];
+
+ return (
+
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+ );
+};
diff --git a/src/editor-v2/components/MessageCard/index.ts b/src/editor-v2/components/MessageCard/index.ts
new file mode 100644
index 000000000..6dea415f5
--- /dev/null
+++ b/src/editor-v2/components/MessageCard/index.ts
@@ -0,0 +1,2 @@
+export {MessageCard} from './MessageCard';
+export type {MessageCardProps, MessageTheme} from './MessageCard';
diff --git a/src/editor-v2/components/Panels/Panels.scss b/src/editor-v2/components/Panels/Panels.scss
new file mode 100644
index 000000000..a7601adb5
--- /dev/null
+++ b/src/editor-v2/components/Panels/Panels.scss
@@ -0,0 +1,37 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}panels';
+
+#{$block} {
+ &__button-wrap {
+ --pceditor-panels-button-gap: 8px;
+ position: absolute;
+ z-index: 1000;
+
+ &_left {
+ bottom: var(--pceditor-panels-button-gap);
+ left: calc(100% + var(--pceditor-panels-button-gap));
+ }
+
+ &_right {
+ bottom: var(--pceditor-panels-button-gap);
+ right: calc(100% + var(--pceditor-panels-button-gap));
+ }
+ }
+
+ &__draggable {
+ position: relative;
+
+ width: 8px;
+ height: 100%;
+ cursor: col-resize;
+ background-color: var(--g-color-line-generic);
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ color: var(--g-color-base-background);
+ }
+}
diff --git a/src/editor-v2/components/Panels/Panels.tsx b/src/editor-v2/components/Panels/Panels.tsx
new file mode 100644
index 000000000..0c128a44b
--- /dev/null
+++ b/src/editor-v2/components/Panels/Panels.tsx
@@ -0,0 +1,77 @@
+import {ArrowLeftFromLine, ArrowRightFromLine, Grip} from '@gravity-ui/icons';
+import {Button, Icon} from '@gravity-ui/uikit';
+import * as React from 'react';
+import {ImperativePanelHandle, Panel, PanelGroup, PanelResizeHandle} from 'react-resizable-panels';
+
+import {editorCn} from '../../utils/cn';
+
+import './Panels.scss';
+
+const b = editorCn('panels');
+
+interface PanelsProps {
+ left: React.ReactElement;
+ middle: React.ReactElement;
+ right: React.ReactElement;
+}
+
+export const Panels = ({left, right, middle}: PanelsProps) => {
+ const leftPanel = React.useRef(null);
+ const rightPanel = React.useRef(null);
+
+ const expandPanel = (reference: React.RefObject) => {
+ const panel = reference.current;
+ if (panel) {
+ panel.expand();
+ }
+ };
+
+ const isCollapsed = {
+ left: leftPanel.current?.isCollapsed() || false,
+ right: rightPanel.current?.isCollapsed() || false,
+ };
+
+ return (
+
+
+ {left}
+
+
+
+ {isCollapsed.left && (
+
+ expandPanel(leftPanel)}
+ >
+
+
+
+ )}
+
+ {middle}
+
+
+ {isCollapsed.right && (
+
+ expandPanel(rightPanel)}
+ >
+
+
+
+ )}
+
+
+ {right}
+
+
+ );
+};
diff --git a/src/editor-v2/components/Sidebar/Sidebar.scss b/src/editor-v2/components/Sidebar/Sidebar.scss
new file mode 100644
index 000000000..1ece6f738
--- /dev/null
+++ b/src/editor-v2/components/Sidebar/Sidebar.scss
@@ -0,0 +1,20 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}sidebar';
+
+#{$block} {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ height: 100%;
+
+ &__block {
+ width: 100%;
+ border-bottom: 1px solid var(--g-color-line-generic);
+ }
+
+ &__tabs {
+ @include custom-scrollbar();
+ }
+}
diff --git a/src/editor-v2/components/Sidebar/Sidebar.tsx b/src/editor-v2/components/Sidebar/Sidebar.tsx
new file mode 100644
index 000000000..267ad9a93
--- /dev/null
+++ b/src/editor-v2/components/Sidebar/Sidebar.tsx
@@ -0,0 +1,28 @@
+import * as React from 'react';
+
+import {editorCn} from '../../utils/cn';
+import Tabs, {TabItemProps} from '../Tabs/Tabs';
+
+import './Sidebar.scss';
+
+const b = editorCn('sidebar');
+
+interface SidebarProps {
+ tabs: TabItemProps[];
+ defaultTab?: string;
+ top?: React.ElementType[];
+ className?: string;
+}
+
+export const Sidebar = ({className, tabs, top = []}: SidebarProps) => {
+ return (
+
+ {top.map((TopComponent, idx) => (
+
+
+
+ ))}
+
+
+ );
+};
diff --git a/src/editor-v2/components/Tabs/Tabs.scss b/src/editor-v2/components/Tabs/Tabs.scss
new file mode 100644
index 000000000..ca98b0cdb
--- /dev/null
+++ b/src/editor-v2/components/Tabs/Tabs.scss
@@ -0,0 +1,73 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}tabs';
+
+#{$block} {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ height: 100%;
+
+ .g-button#{$block}__item {
+ color: var(--g-color-text-hint);
+ font-weight: 500;
+ letter-spacing: 0.5px;
+ cursor: pointer;
+ font-size: var(--g-text-code-inline-1-font-size);
+ line-height: var(--g-text-code-inline-2-line-height);
+ align-items: center;
+
+ &_active {
+ pointer-events: none;
+ color: var(--g-color-text-primary);
+ }
+ }
+
+ &__tabs-wrapper {
+ width: 100%;
+ border-bottom: 1px solid var(--g-color-line-generic);
+ display: inline-flex;
+ flex-direction: row;
+ padding: 12px 4px;
+ flex: 0 0 auto;
+ box-sizing: border-box;
+ overflow-x: auto;
+ position: sticky;
+ top: 0;
+ background-color: var(--g-color-base-background);
+ /* This ensures the background is visible when sticky */
+ z-index: 2;
+ /* Higher z-index for parent tabs-wrapper */
+ }
+
+ /* Make sure any nested tabs component also has proper overflow behavior */
+ #{$block} {
+ overflow: visible;
+
+ /* Ensure nested tabs-wrapper elements are also sticky but positioned below parent tabs-wrapper */
+ /* This selector specifically targets tabs-wrapper elements inside a nested tabs component */
+ #{$block} &__tabs-wrapper {
+ position: sticky;
+ /* Position below parent tabs-wrapper */
+ z-index: 1;
+ background-color: var(--g-color-base-background);
+ }
+ }
+
+ &__body {
+ flex: 1;
+ min-height: 1px;
+ height: 100%;
+ }
+
+ &__item {
+ @include custom-scrollbar();
+ overflow-y: auto;
+ max-height: 100%;
+
+ &_padding {
+ padding: 16px 12px;
+ }
+ }
+}
diff --git a/src/editor-v2/components/Tabs/Tabs.tsx b/src/editor-v2/components/Tabs/Tabs.tsx
new file mode 100644
index 000000000..e15087b32
--- /dev/null
+++ b/src/editor-v2/components/Tabs/Tabs.tsx
@@ -0,0 +1,86 @@
+import {Button} from '@gravity-ui/uikit';
+import * as React from 'react';
+
+import {editorCn} from '../../utils/cn';
+
+import './Tabs.scss';
+
+const b = editorCn('tabs');
+
+export interface TabItemProps {
+ id: string;
+ title: string;
+ component: React.ElementType;
+ withPadding?: boolean;
+}
+
+export interface TabsProps {
+ className?: string;
+ items: TabItemProps[];
+ defaultTab?: string | null;
+}
+
+const Tabs = ({className, items, defaultTab}: TabsProps) => {
+ const [currentTab, setCurrentTab] = React.useState(defaultTab);
+
+ const activeTab = React.useMemo(() => {
+ if (currentTab) {
+ const findTab = items.find(({id}) => id === currentTab);
+ if (findTab) {
+ return findTab;
+ }
+ }
+
+ return items[0] || null;
+ }, [currentTab, items]);
+
+ const [isPaddingEnabled, setIsPaddingEnabled] = React.useState(
+ activeTab?.withPadding || true,
+ );
+
+ const handleClick = React.useCallback(
+ (tabItem: TabItemProps) => () => {
+ setCurrentTab(tabItem.id);
+ setIsPaddingEnabled(tabItem.withPadding || false);
+ },
+ [],
+ );
+
+ const TabComponent = React.useMemo(() => {
+ return activeTab?.component;
+ }, [activeTab]);
+
+ return (
+
+ {items.length > 1 && (
+
+ {items.map((item) => {
+ const isActive = item.id === activeTab.id;
+
+ return (
+
+ {item.title}
+
+ );
+ })}
+
+ )}
+
+ {TabComponent && (
+
+ )}
+
+
+ );
+};
+
+export default Tabs;
diff --git a/src/editor-v2/constants.ts b/src/editor-v2/constants.ts
new file mode 100644
index 000000000..22c3e53f2
--- /dev/null
+++ b/src/editor-v2/constants.ts
@@ -0,0 +1 @@
+export const ZOOM_STEPS = [25, 33, 50, 75, 100, 125, 150, 200, 250, 300];
diff --git a/src/editor-v2/constants/messages.ts b/src/editor-v2/constants/messages.ts
new file mode 100644
index 000000000..21fc10854
--- /dev/null
+++ b/src/editor-v2/constants/messages.ts
@@ -0,0 +1,12 @@
+export const MESSAGES = {
+ NO_BLOCK_SELECTED: {
+ title: 'Блок не выбран',
+ description:
+ 'Выберите блок на экране или в слоях в левом меню для редактирования его свойств',
+ },
+ UNSUPPORTED_BLOCK: {
+ title: 'Неподдерживаемый блок',
+ description:
+ 'Данный тип блока не поддерживается в редакторе. Но вы также можете отредактировать его конфигурацию вручную в меню RAW.',
+ },
+} as const;
diff --git a/src/editor-v2/containers/BigOverlay/BigOverlay.scss b/src/editor-v2/containers/BigOverlay/BigOverlay.scss
new file mode 100644
index 000000000..1d09dfdb1
--- /dev/null
+++ b/src/editor-v2/containers/BigOverlay/BigOverlay.scss
@@ -0,0 +1,31 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}big-overlay';
+
+#{$block} {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ overflow: hidden;
+
+ $border: 3px var(--g-color-base-brand) solid;
+
+ &__border {
+ pointer-events: none;
+ position: absolute;
+ width: 40px;
+ height: 40px;
+ margin-left: -20px;
+ margin-top: -20px;
+ background-color: var(--g-color-base-brand);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 10px;
+ //transition: top .1s ease, left .1s ease, height .1s ease, width .1s ease;
+ }
+}
diff --git a/src/editor-v2/containers/BigOverlay/BigOverlay.tsx b/src/editor-v2/containers/BigOverlay/BigOverlay.tsx
new file mode 100644
index 000000000..34d50fefc
--- /dev/null
+++ b/src/editor-v2/containers/BigOverlay/BigOverlay.tsx
@@ -0,0 +1,81 @@
+import {Stop} from '@gravity-ui/icons';
+import * as React from 'react';
+
+import {usePostMessageAPIListener} from '../../../common/postMessage';
+import {IframeContext} from '../../context/iframeContext';
+import {useMainEditorStore} from '../../hooks/useMainEditorStore';
+import {editorCn} from '../../utils/cn';
+
+import './BigOverlay.scss';
+
+const b = editorCn('big-overlay');
+
+const BigOverlay = ({className}: {className?: string}) => {
+ const {zoom, manipulateOverlayMode} = useMainEditorStore();
+ const {iframeElement} = React.useContext(IframeContext);
+ const [mousePosition, setMousePosition] = React.useState<{x: number; y: number} | undefined>(
+ undefined,
+ );
+ const [source, setSource] = React.useState<'main' | 'iframe'>('main');
+
+ const onMouseUp = React.useCallback(() => {
+ setMousePosition(undefined);
+ }, []);
+
+ const onIframeMouseEvent = React.useCallback((position: {x: number; y: number}) => {
+ setMousePosition(position);
+ setSource('iframe');
+ }, []);
+
+ usePostMessageAPIListener('ON_MOUSE_UP', onMouseUp);
+ usePostMessageAPIListener('ON_MOUSE_MOVE', onIframeMouseEvent);
+
+ React.useEffect(() => {
+ const onEditorMouseEvent = (event: MouseEvent) => {
+ setMousePosition({x: event.clientX, y: event.clientY});
+ setSource('main');
+ };
+
+ document.addEventListener('mousemove', onEditorMouseEvent);
+ document.addEventListener('mousedown', onEditorMouseEvent);
+
+ return () => {
+ document.removeEventListener('mousemove', onEditorMouseEvent);
+ document.removeEventListener('mousedown', onEditorMouseEvent);
+ };
+ }, []);
+
+ const realPositions = React.useMemo(() => {
+ if (mousePosition) {
+ const {x, y} = mousePosition;
+ const iframeRect = iframeElement?.getClientRects().item(0);
+ if (iframeRect) {
+ const zoomedX = (x * zoom) / 100;
+ const zoomedY = (y * zoom) / 100;
+ const newX = source === 'main' ? x : zoomedX + iframeRect.x;
+ const newY = source === 'main' ? y : zoomedY + iframeRect.y;
+ return {x: newX, y: newY};
+ }
+ }
+
+ return undefined;
+ }, [mousePosition, source, iframeElement, zoom]);
+
+ return (
+
+ {realPositions && manipulateOverlayMode ? (
+
+
+
+ ) : null}
+
+ );
+};
+
+export default BigOverlay;
diff --git a/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss b/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss
new file mode 100644
index 000000000..ceb6c8859
--- /dev/null
+++ b/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.scss
@@ -0,0 +1,28 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}block-config-form';
+
+#{$block} {
+ &_empty {
+ padding: 16px 12px;
+ }
+
+ &__title {
+ @include text-subheader-3;
+ }
+
+ &__form {
+ padding: 12px 0;
+ }
+
+ &__empty {
+ padding: 12px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ height: 100%;
+ width: 100%;
+ }
+}
diff --git a/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.tsx b/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.tsx
new file mode 100644
index 000000000..52e878203
--- /dev/null
+++ b/src/editor-v2/containers/BlockConfigForm/BlockConfigForm.tsx
@@ -0,0 +1,68 @@
+import _ from 'lodash';
+
+import {DynamicFormValue, FormGenerator} from '../../../form-generator';
+import {MessageCard} from '../../components/MessageCard';
+import {MESSAGES} from '../../constants/messages';
+import {useMainEditorStore} from '../../hooks/useMainEditorStore';
+import {generateChildrenPathFromArray} from '../../utils';
+import {editorCn} from '../../utils/cn';
+
+import './BlockConfigForm.scss';
+
+const b = editorCn('block-config-form');
+
+interface BlockConfigFormProps {
+ className?: string;
+}
+
+const BlockConfigForm = ({className}: BlockConfigFormProps) => {
+ const {selectedBlock, content, blocks, subBlocks, updateField} = useMainEditorStore();
+
+ const currentBlockPath = selectedBlock ? generateChildrenPathFromArray(selectedBlock) : '[]';
+
+ const currentConfig = _.get(content.blocks, currentBlockPath || '');
+ const currentSchema = [...blocks, ...subBlocks].find(({type}) => type === currentConfig?.type);
+
+ const onUpdate = (key: string, value: DynamicFormValue) => {
+ updateField('blocks' + currentBlockPath + '.' + key, value);
+ };
+
+ if (!currentConfig) {
+ return (
+
+
+
+ );
+ }
+
+ if (!currentSchema) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
{currentSchema.schema.name}
+
+
+
+
+ );
+};
+
+export default BlockConfigForm;
diff --git a/src/editor-v2/containers/BlocksList/BlocksList.scss b/src/editor-v2/containers/BlocksList/BlocksList.scss
new file mode 100644
index 000000000..3fb3d3438
--- /dev/null
+++ b/src/editor-v2/containers/BlocksList/BlocksList.scss
@@ -0,0 +1,88 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}blocks-list';
+
+#{$block} {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+
+ &__search {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ }
+
+ &__title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 4px;
+ background: none;
+ border: none;
+ cursor: pointer;
+ width: 100%;
+ text-align: left;
+ user-select: none;
+ outline: none;
+
+ color: var(--g-color-text-secondary);
+ font-size: var(--g-text-subheader-1-font-size);
+ line-height: var(--g-text-subheader-1-line-height);
+ font-weight: 500;
+
+ border-radius: $editorControlBorderRadius;
+ transition: background-color $editorTransitionTime;
+
+ &:hover {
+ background-color: var(--g-color-base-generic-hover);
+ }
+
+ &:focus {
+ outline: none;
+ }
+
+ &-icon {
+ flex-shrink: 0;
+ color: var(--g-color-text-hint);
+ transition: transform $editorTransitionTime;
+ }
+ }
+
+ &__group {
+ &:first-child {
+ margin-top: 0;
+ }
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ &_collapsed {
+ #{$block}__title-icon {
+ transform: rotate(0deg);
+ }
+ }
+
+ &:not(&_collapsed) {
+ #{$block}__title-icon {
+ transform: rotate(0deg);
+ }
+ }
+
+ &-items {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ gap: 8px;
+ margin-top: 8px;
+ }
+ }
+
+ &__search {
+ &-icon {
+ margin-left: 8px;
+ margin-right: 8px;
+ }
+ }
+}
diff --git a/src/editor-v2/containers/BlocksList/BlocksList.tsx b/src/editor-v2/containers/BlocksList/BlocksList.tsx
new file mode 100644
index 000000000..2a658e5bb
--- /dev/null
+++ b/src/editor-v2/containers/BlocksList/BlocksList.tsx
@@ -0,0 +1,148 @@
+import {ChevronDown, ChevronRight, Folder, FolderOpen, Magnifier} from '@gravity-ui/icons';
+import {DropdownMenu, Icon, TextInput} from '@gravity-ui/uikit';
+import * as React from 'react';
+
+import {ItemConfig} from '../../../common/types';
+import {ClassNameProps} from '../../../models';
+import BlockCard from '../../components/BlockCard/BlockCard';
+import {useMainEditorStore} from '../../hooks/useMainEditorStore';
+import {editorCn} from '../../utils/cn';
+
+import './BlocksList.scss';
+
+const b = editorCn('blocks-list');
+
+interface BlockGroups {
+ [key: string]: ItemConfig[];
+}
+
+interface BlockListProps extends ClassNameProps {}
+
+const BlocksList = ({className}: BlockListProps) => {
+ const {blocks, enableInsertMode} = useMainEditorStore();
+ const [search, setSearch] = React.useState('');
+ const [collapsedGroups, setCollapsedGroups] = React.useState>(new Set());
+
+ const onMouseDown = React.useCallback(
+ (blockType: string) => {
+ enableInsertMode(blockType);
+ },
+ [enableInsertMode],
+ );
+
+ const toggleGroup = React.useCallback((groupKey: string) => {
+ setCollapsedGroups((prev) => {
+ const newSet = new Set(prev);
+ if (newSet.has(groupKey)) {
+ newSet.delete(groupKey);
+ } else {
+ newSet.add(groupKey);
+ }
+ return newSet;
+ });
+ }, []);
+
+ const expandAll = React.useCallback(() => {
+ setCollapsedGroups(new Set());
+ }, []);
+
+ const groups = React.useMemo(() => {
+ return blocks.reduce((acc, currentBlock) => {
+ const group = currentBlock.schema.group;
+ if (
+ search &&
+ currentBlock.type.toLowerCase().indexOf(search.toLowerCase()) === -1 &&
+ currentBlock.schema.name.toLowerCase().indexOf(search.toLowerCase()) === -1
+ ) {
+ return acc;
+ }
+ if (group) {
+ if (!acc[group]) {
+ /* eslint-disable no-param-reassign */
+ acc[group] = [];
+ }
+ acc[group].push(currentBlock);
+ } else {
+ if (!acc['Other']) {
+ /* eslint-disable no-param-reassign */
+ acc['Other'] = [];
+ }
+ acc['Other'].push(currentBlock);
+ }
+
+ return acc;
+ }, {} as BlockGroups);
+ }, [blocks, search]);
+
+ const collapseAll = React.useCallback(() => {
+ setCollapsedGroups(new Set(Object.keys(groups)));
+ }, [groups]);
+
+ const allGroupsExpanded = collapsedGroups.size === 0;
+ const allGroupsCollapsed = collapsedGroups.size === Object.keys(groups).length;
+
+ return (
+
+
+ }
+ />
+ ,
+ disabled: allGroupsExpanded,
+ },
+ {
+ action: collapseAll,
+ text: 'Свернуть все',
+ iconStart: ,
+ disabled: allGroupsCollapsed,
+ },
+ ]}
+ />
+
+ {Object.entries(groups).map(([key, groupBlocks]) => {
+ const isCollapsed = collapsedGroups.has(key);
+ return (
+
+
toggleGroup(key)}
+ type="button"
+ >
+
+ {key}
+
+ {!isCollapsed && (
+
+ {groupBlocks.map(({type, schema: {name, previewImg}}) => (
+
+ ))}
+
+ )}
+
+ );
+ })}
+
+ );
+};
+
+export default BlocksList;
diff --git a/src/editor-v2/containers/Editor/Editor.scss b/src/editor-v2/containers/Editor/Editor.scss
new file mode 100644
index 000000000..da304c330
--- /dev/null
+++ b/src/editor-v2/containers/Editor/Editor.scss
@@ -0,0 +1,48 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}editor';
+
+#{$block} {
+ margin: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
+ position: relative;
+
+ &__header {
+ height: $headerHeight;
+ width: 100%;
+ border-bottom: 1px solid var(--g-color-line-generic);
+ }
+
+ &__body {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex: 1;
+ width: 100%;
+ max-height: calc(100% - $headerHeight);
+ }
+
+ &__canvas {
+ flex: 1;
+ }
+
+ &__overlay {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ z-index: 100;
+ }
+
+ &__debug-store {
+ position: fixed;
+ right: 8px;
+ bottom: 8px;
+ z-index: 1000;
+ }
+}
diff --git a/src/editor-v2/containers/Editor/Editor.tsx b/src/editor-v2/containers/Editor/Editor.tsx
new file mode 100644
index 000000000..edc2c39ec
--- /dev/null
+++ b/src/editor-v2/containers/Editor/Editor.tsx
@@ -0,0 +1,88 @@
+import * as React from 'react';
+
+import {PageContentWithNavigation} from '../../../models';
+import {Panels} from '../../components/Panels/Panels';
+import {Sidebar} from '../../components/Sidebar/Sidebar';
+import BigOverlay from '../../containers/BigOverlay/BigOverlay';
+import MiddleScreen from '../../containers/MiddleScreen/MiddleScreen';
+import {MainEditorStoreProvider} from '../../context/editorStore';
+import {IframeProvider} from '../../context/iframeContext';
+import {useEditorTabs} from '../../hooks/useEditorTabs';
+import useMainEditorInitialize from '../../hooks/useMainEditorInitialize';
+import {useMainEditorStore} from '../../hooks/useMainEditorStore';
+import {editorCn} from '../../utils/cn';
+import Source from '../Source/Source';
+import ViewSwitches from '../ViewSwitches/ViewSwitches';
+
+import './Editor.scss';
+
+const b = editorCn('editor');
+
+interface SidebarTabComponent {
+ id: string;
+ title: string;
+ component: React.ElementType;
+}
+interface EditorViewProps {
+ onUpdate?: (pageContent: PageContentWithNavigation) => void;
+ initialUrl: string;
+ initialContent?: PageContentWithNavigation;
+ disableUrlField?: boolean;
+ componentsConfig?: {
+ middleTop?: React.ElementType;
+ leftTop?: React.ElementType[];
+ rightTop?: React.ElementType[];
+ leftTabs?: SidebarTabComponent[];
+ rightTabs?: SidebarTabComponent[];
+ };
+}
+
+const EditorView = ({componentsConfig = {}, initialContent}: EditorViewProps) => {
+ const store = useMainEditorStore();
+ const {manipulateOverlayMode, disableMode} = store;
+
+ useMainEditorInitialize(initialContent);
+
+ // Disable insert mode on any MouseUp event
+ // Maybe should be attached to body
+ const onMouseUp = React.useCallback(
+ (e: React.MouseEvent) => {
+ if (manipulateOverlayMode) {
+ e.preventDefault();
+ disableMode();
+ }
+ },
+ [disableMode, manipulateOverlayMode],
+ );
+ const {left, right} = useEditorTabs(componentsConfig);
+
+ return (
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
+
+
+
}
+ right={
+
+ }
+ middle={
}
+ />
+
+
+
+ );
+};
+
+export const Editor = (props: EditorViewProps) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss b/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss
new file mode 100644
index 000000000..78efa4cf6
--- /dev/null
+++ b/src/editor-v2/containers/GlobalConfig/GlobalConfig.scss
@@ -0,0 +1,10 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}global-config';
+
+#{$block} {
+ &__title {
+ @include text-subheader-3;
+ }
+}
diff --git a/src/editor-v2/containers/GlobalConfig/GlobalConfig.tsx b/src/editor-v2/containers/GlobalConfig/GlobalConfig.tsx
new file mode 100644
index 000000000..d53b31a0b
--- /dev/null
+++ b/src/editor-v2/containers/GlobalConfig/GlobalConfig.tsx
@@ -0,0 +1,32 @@
+import {DynamicFormValue, FormGenerator} from '../../../form-generator';
+import {useMainEditorStore} from '../../hooks/useMainEditorStore';
+import {editorCn} from '../../utils/cn';
+
+import './GlobalConfig.scss';
+
+const b = editorCn('global-config');
+
+export interface GlobalConfigProps {
+ className?: string;
+}
+
+const GlobalConfig = ({className}: GlobalConfigProps) => {
+ const {global, content, updateField} = useMainEditorStore();
+
+ const onUpdate = (key: string, value: DynamicFormValue) => {
+ updateField('navigation.' + key, value);
+ };
+
+ return (
+
+ );
+};
+
+export default GlobalConfig;
diff --git a/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss b/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss
new file mode 100644
index 000000000..d815aa207
--- /dev/null
+++ b/src/editor-v2/containers/MiddleScreen/MiddleScreen.scss
@@ -0,0 +1,108 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}middle-screen';
+
+#{$block} {
+ height: 100%;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+
+ &__topbar {
+ flex: 0 0 auto;
+ }
+
+ &__content {
+ flex: 1 1 auto;
+ overflow-y: auto;
+ // height: 100%;
+ background-color: var(--g-color-text-secondary);
+
+ &_fullscreen {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ z-index: 1000;
+ background-color: #000;
+ }
+ }
+
+ &__wrapper {
+ width: 100%;
+ height: 100%;
+ margin: auto;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ overflow: hidden;
+ position: relative;
+ }
+
+ &__canvas {
+ @include custom-scrollbar();
+ background-color: var(--g-color-base-background);
+ transform-origin: center top;
+ position: absolute;
+ height: 100%;
+ max-width: 100%;
+
+ &_fullscreen {
+ overflow: hidden;
+ width: 100%;
+ height: 100%;
+ max-width: 100%;
+ }
+ }
+
+ &__iframe {
+ display: block;
+ margin: 0 auto;
+
+ &_fullscreen {
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ &__exit-preview {
+ position: fixed;
+ opacity: 0.5;
+ top: 16px;
+ right: 16px;
+ z-index: 1001;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ &__overlay {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ }
+
+ &__border {
+ position: absolute;
+ border: 3px var(--g-color-line-brand) solid;
+ border-radius: 10px;
+ }
+
+ &__loading {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background: var(--g-color-base-background);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+}
diff --git a/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx b/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx
new file mode 100644
index 000000000..65573135b
--- /dev/null
+++ b/src/editor-v2/containers/MiddleScreen/MiddleScreen.tsx
@@ -0,0 +1,105 @@
+import {Xmark} from '@gravity-ui/icons';
+import {Button, Icon, Loader} from '@gravity-ui/uikit';
+import * as React from 'react';
+
+import {usePostMessageAPIListener} from '../../../common/postMessage';
+import {IframeContext} from '../../context/iframeContext';
+import {useMainEditorStore} from '../../hooks/useMainEditorStore';
+import {editorCn} from '../../utils/cn';
+import Overlay from '../Overlay/Overlay';
+
+import './MiddleScreen.scss';
+
+const b = editorCn('middle-screen');
+
+interface MiddleScreenProps {
+ className?: string;
+ CustomTop?: React.ElementType;
+}
+
+const MiddleScreen = ({className, CustomTop}: MiddleScreenProps) => {
+ const {zoom, initialized, deviceWidth, isPreviewMode, togglePreviewMode} = useMainEditorStore();
+ const {url, setIframeElement} = React.useContext(IframeContext);
+ const [canvasRef, setCanvasRef] = React.useState(null);
+ const [height, setHeight] = React.useState(0);
+
+ const canvasStyle = React.useMemo(
+ () => ({
+ transform: isPreviewMode ? 'none' : `scale(${zoom}%)`,
+ height: isPreviewMode ? '100%' : `${(100 / zoom) * 100}%`,
+ width: isPreviewMode ? '100%' : `${(100 / zoom) * 100}%`,
+ maxWidth: isPreviewMode ? '100%' : `${(100 / zoom) * 100}%`,
+ }),
+ [isPreviewMode, zoom],
+ );
+
+ const onResize = React.useCallback(
+ (newHeight: number) => {
+ setHeight(newHeight + 100);
+ },
+ [setHeight],
+ );
+
+ usePostMessageAPIListener('ON_RESIZE', ({height: newHeight}) => {
+ onResize(newHeight);
+ });
+
+ usePostMessageAPIListener('ON_INIT', ({height: newHeight}) => {
+ onResize(newHeight);
+ });
+
+ return (
+
+ {CustomTop && !isPreviewMode ? (
+
+
+
+ ) : null}
+
+
+ );
+};
+
+export default MiddleScreen;
diff --git a/src/editor-v2/containers/Overlay/Overlay.scss b/src/editor-v2/containers/Overlay/Overlay.scss
new file mode 100644
index 000000000..ce0926932
--- /dev/null
+++ b/src/editor-v2/containers/Overlay/Overlay.scss
@@ -0,0 +1,94 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}overlay';
+
+#{$block} {
+ $border-width: 3px;
+ $border-select: 5px var(--g-color-base-brand) solid;
+ $border: $border-width var(--g-color-base-brand) solid;
+ $border-radius: 24px;
+
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+
+ &__border {
+ position: absolute;
+ border: $border;
+ border-radius: $border-radius;
+ box-sizing: border-box;
+ box-shadow: 4px 4px 8px 0 var(--g-color-sfx-shadow);
+ z-index: 100;
+
+ &_hover {
+ opacity: 0.5;
+ z-index: 99;
+ }
+ }
+
+ &__line {
+ position: absolute;
+ //border: $border-2;
+ box-sizing: border-box;
+ border-radius: $border-radius;
+ z-index: 10;
+
+ &_position {
+ &_top {
+ border-top: $border-select;
+ border-left: $border-select;
+ }
+
+ &_bottom {
+ border-right: $border-select;
+ border-bottom: $border-select;
+ }
+
+ &_left {
+ border-top: $border-select;
+ border-left: $border-select;
+ }
+
+ &_right {
+ border-right: $border-select;
+ border-bottom: $border-select;
+ }
+ }
+ }
+
+ &__actions {
+ pointer-events: auto;
+ position: absolute;
+ bottom: -$border-width;
+ left: 50%;
+ transform: translate(-50%, 100%);
+ z-index: 1000;
+
+ display: flex;
+ align-items: flex-start;
+ background: transparent;
+ }
+
+ &__actions-box {
+ &_main {
+ display: flex;
+ gap: 2px;
+ align-items: center;
+ padding: 1px 8px 4px;
+ background-color: var(--g-color-base-brand);
+ border-radius: 0px 0px 8px 8px;
+ }
+
+ &_reorder {
+ padding: 3px 4px;
+ }
+ }
+
+ &__reorder-icon {
+ color: var(--g-color-base-brand);
+ }
+}
diff --git a/src/editor-v2/containers/Overlay/Overlay.tsx b/src/editor-v2/containers/Overlay/Overlay.tsx
new file mode 100644
index 000000000..fcb918141
--- /dev/null
+++ b/src/editor-v2/containers/Overlay/Overlay.tsx
@@ -0,0 +1,189 @@
+import {ChevronDown, ChevronUp, Copy, TrashBin} from '@gravity-ui/icons';
+import {Button, Icon} from '@gravity-ui/uikit';
+import * as React from 'react';
+import _ from 'lodash';
+
+import {usePostMessageAPIListener} from '../../../common/postMessage';
+import {useMainEditorStore} from '../../hooks/useMainEditorStore';
+import {editorCn} from '../../utils/cn';
+
+import './Overlay.scss';
+
+const b = editorCn('overlay');
+
+interface OverlayProps {
+ className?: string;
+ canvasElement?: HTMLDivElement | null;
+}
+interface InsertLineProps {
+ top: number;
+ left: number;
+ height: number;
+ width: number;
+ position: string;
+}
+
+const Overlay = ({className, canvasElement}: OverlayProps) => {
+ const {
+ height,
+ selectedBlock,
+ setSelectedBlock,
+ deleteBlock,
+ duplicateBlock,
+ manipulateOverlayMode,
+ reorderBlock,
+ } = useMainEditorStore();
+ const [insertLineBox, setInsertLineBox] = React.useState(
+ undefined,
+ );
+ const [hoverBorders, setHoverBorders] = React.useState(null);
+ const [blockBorders, setBlockBorders] = React.useState(null);
+
+ // Listen for updates to the selected block's position
+ usePostMessageAPIListener(
+ 'ON_UPDATE_BLOCK_SELECTION',
+ ({rect}) => {
+ setBlockBorders(selectedBlock && rect ? rect : null);
+ },
+ [selectedBlock],
+ );
+
+ // Update blockBorders when selectedBlock changes
+ React.useEffect(() => {
+ if (!selectedBlock) {
+ setBlockBorders(null);
+ }
+ }, [selectedBlock]);
+
+ // Auto scroll to the selected block when blockBorders changes
+ React.useEffect(() => {
+ if (blockBorders && canvasElement) {
+ // Calculate the scroll position to center the block in the viewport
+ const canvasHeight = canvasElement.clientHeight;
+ const scrollPosition = blockBorders.top - canvasHeight / 2 + blockBorders.height / 2;
+
+ // Scroll the canvas element to the calculated position with smooth behavior
+ canvasElement.scrollTo({
+ top: Math.max(0, scrollPosition),
+ behavior: 'smooth',
+ });
+ }
+ }, [blockBorders, canvasElement]);
+
+ const margin = 0;
+
+ usePostMessageAPIListener('ON_HOVER_BLOCK', ({rect, position}) => {
+ setHoverBorders(rect || null);
+ if (rect && position) {
+ setInsertLineBox({
+ left: rect.x,
+ top: rect.y,
+ height: rect.height,
+ width: rect.width,
+ position: position,
+ });
+ }
+ });
+
+ usePostMessageAPIListener(
+ 'ON_CLICK_BLOCK',
+ ({path}) => {
+ if (!selectedBlock && path) {
+ setSelectedBlock(path);
+ } else if (selectedBlock && _.isEqual(selectedBlock, path)) {
+ setSelectedBlock(null);
+ setBlockBorders(null);
+ } else {
+ setSelectedBlock(path);
+ }
+ },
+ [selectedBlock],
+ );
+
+ const handleMoveUp = () => {
+ if (!selectedBlock) return;
+ const destination = [...selectedBlock];
+ destination[destination.length - 1] = destination[destination.length - 1] - 1;
+ reorderBlock(selectedBlock, destination, 'prepend');
+ };
+
+ const handleMoveDown = () => {
+ if (!selectedBlock) return;
+ const destination = [...selectedBlock];
+ destination[destination.length - 1] = destination[destination.length - 1] + 1;
+ reorderBlock(selectedBlock, destination, 'append');
+ };
+
+ return (
+
+ {blockBorders ? (
+
+
+
+
+
+
+
+
+ selectedBlock && duplicateBlock(selectedBlock)}
+ >
+
+
+ selectedBlock && deleteBlock(selectedBlock)}
+ >
+
+
+
+
+
+
+
+
+
+
+ ) : null}
+ {hoverBorders ? (
+
+ ) : null}
+ {manipulateOverlayMode && hoverBorders && insertLineBox ? (
+
+ ) : null}
+
+ );
+};
+
+export default Overlay;
diff --git a/src/editor-v2/containers/Source/Source.scss b/src/editor-v2/containers/Source/Source.scss
new file mode 100644
index 000000000..14ed10069
--- /dev/null
+++ b/src/editor-v2/containers/Source/Source.scss
@@ -0,0 +1,22 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}source';
+
+#{$block} {
+ width: 100%;
+ display: inline-flex;
+ box-sizing: border-box;
+ align-items: center;
+ flex-direction: row;
+ padding: 16px 12px;
+
+ &__icon {
+ flex: 0 0 auto;
+ margin-right: var(--g-spacing-1);
+ }
+
+ &__text {
+ flex: 1;
+ }
+}
diff --git a/src/editor-v2/containers/Source/Source.tsx b/src/editor-v2/containers/Source/Source.tsx
new file mode 100644
index 000000000..9202d9184
--- /dev/null
+++ b/src/editor-v2/containers/Source/Source.tsx
@@ -0,0 +1,48 @@
+import {ArrowRotateRight} from '@gravity-ui/icons';
+import {Button, Icon, TextInput} from '@gravity-ui/uikit';
+import * as React from 'react';
+
+import {IframeContext} from '../../context/iframeContext';
+import {useMainEditorStore} from '../../hooks/useMainEditorStore';
+import {editorCn} from '../../utils/cn';
+
+import './Source.scss';
+
+const b = editorCn('source');
+
+const Source = () => {
+ const {resetInitialize} = useMainEditorStore();
+ const {disableUrlField, url, setUrl} = React.useContext(IframeContext);
+
+ const onUpdateUrl = React.useCallback(
+ (value: string) => {
+ setUrl(value);
+ resetInitialize();
+ },
+ [resetInitialize, setUrl],
+ );
+
+ const reloadIframe = () => {
+ setUrl('');
+ setTimeout(() => {
+ setUrl(url);
+ }, 0);
+ };
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default Source;
diff --git a/src/editor-v2/containers/SourceCode/SourceCode.scss b/src/editor-v2/containers/SourceCode/SourceCode.scss
new file mode 100644
index 000000000..ce0d7aaff
--- /dev/null
+++ b/src/editor-v2/containers/SourceCode/SourceCode.scss
@@ -0,0 +1,58 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}source-code';
+
+#{$block} {
+ height: 100%;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+
+ &__header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ &__title {
+ font-weight: 500;
+ font-size: 13px;
+ line-height: 18px;
+ color: var(--g-color-text-secondary);
+ }
+
+ &__code {
+ @include custom-scrollbar();
+ white-space: pre;
+ @include text-code-inline-1;
+ padding: 6px 8px;
+ overflow: auto;
+ border: 1px solid var(--g-color-line-generic);
+ border-radius: 6px;
+ position: relative;
+ flex: 1;
+ }
+
+ &__content {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ background: transparent;
+ border: none;
+ outline: none;
+ resize: none;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ color: inherit;
+ }
+
+ &__controls {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+ align-items: center;
+ }
+}
diff --git a/src/editor-v2/containers/SourceCode/SourceCode.tsx b/src/editor-v2/containers/SourceCode/SourceCode.tsx
new file mode 100644
index 000000000..843513f59
--- /dev/null
+++ b/src/editor-v2/containers/SourceCode/SourceCode.tsx
@@ -0,0 +1,123 @@
+import {Pencil} from '@gravity-ui/icons';
+import {Button, ClipboardButton, Icon, SegmentedRadioGroup} from '@gravity-ui/uikit';
+import yaml from 'js-yaml';
+import _ from 'lodash';
+import * as React from 'react';
+
+import {PageContentWithNavigation} from '../../../models';
+import {useMainEditorStore} from '../../hooks/useMainEditorStore';
+import {generateChildrenPathFromArray} from '../../utils';
+import {editorCn} from '../../utils/cn';
+import {UpdateModal} from './UpdateModal/UpdateModal';
+import {MessageCard} from '../../components/MessageCard/MessageCard';
+import {MESSAGES} from '../../constants/messages';
+
+import './SourceCode.scss';
+
+const b = editorCn('source-code');
+
+interface SourceCodeProps {
+ className?: string;
+ showSelectedBlockOnly?: boolean;
+}
+
+const formatOptions = [
+ {value: 'yaml', content: 'YAML'},
+ {value: 'json', content: 'JSON'},
+];
+
+const SourceCode = ({className, showSelectedBlockOnly = false}: SourceCodeProps) => {
+ const {content, setContent, selectedBlock} = useMainEditorStore();
+ const [isOpen, setIsOpen] = React.useState(false);
+ const [format, setFormat] = React.useState<'yaml' | 'json'>('yaml');
+
+ const handleUpdate = (tempConfig: string) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let object: any;
+
+ try {
+ if (tempConfig.trim().startsWith('{') && tempConfig.trim().endsWith('}')) {
+ object = JSON.parse(tempConfig);
+ } else {
+ object = yaml.load(tempConfig);
+ }
+ } catch {
+ // eslint-disable-next-line no-console
+ console.error('JSON.parse failed');
+ return;
+ }
+
+ if (showSelectedBlockOnly && selectedBlock) {
+ // Update only the selected block
+ const currentBlockPath = generateChildrenPathFromArray(selectedBlock);
+ const newContent = _.cloneDeep(content);
+ _.set(newContent.blocks, currentBlockPath, object);
+ setContent(newContent);
+ } else if (object) {
+ // Update the entire content
+ setContent(object as PageContentWithNavigation);
+ }
+
+ setIsOpen(false);
+ };
+
+ const textContent = React.useMemo(() => {
+ if (showSelectedBlockOnly && selectedBlock) {
+ const currentBlockPath = generateChildrenPathFromArray(selectedBlock);
+ const currentConfig = _.get(content.blocks, currentBlockPath || '');
+
+ if (currentConfig) {
+ return format === 'yaml'
+ ? yaml.dump(currentConfig)
+ : JSON.stringify(currentConfig, null, 2);
+ }
+
+ return 'No block selected';
+ }
+
+ return format === 'yaml' ? yaml.dump(content) : JSON.stringify(content, null, 2);
+ }, [format, content, showSelectedBlockOnly, selectedBlock]);
+
+ if (!selectedBlock && showSelectedBlockOnly) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
setFormat(value as 'yaml' | 'json')}
+ />
+
+ setIsOpen(true)}>
+
+ Edit
+
+
+
+
+
+
setIsOpen(false)}
+ isOpen={isOpen}
+ initialConfig={textContent}
+ />
+
+ );
+};
+
+export default SourceCode;
diff --git a/src/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.scss b/src/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.scss
new file mode 100644
index 000000000..995d31e7a
--- /dev/null
+++ b/src/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.scss
@@ -0,0 +1,14 @@
+@import '../../../styles/variables.scss';
+@import '../../../styles/mixins.scss';
+
+$block: '.#{$ns}source-code-update-modal';
+
+#{$block} {
+ &__alert {
+ margin-bottom: 8px;
+ }
+
+ &__textarea textarea.g-text-area__control {
+ @include text-code-inline-1;
+ }
+}
diff --git a/src/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx b/src/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx
new file mode 100644
index 000000000..2ec37da74
--- /dev/null
+++ b/src/editor-v2/containers/SourceCode/UpdateModal/UpdateModal.tsx
@@ -0,0 +1,56 @@
+import {Alert, Dialog, TextArea} from '@gravity-ui/uikit';
+import * as React from 'react';
+
+import {editorCn} from '../../../utils/cn';
+
+import './UpdateModal.scss';
+
+const b = editorCn('source-code-update-modal');
+
+interface UpdateModalProps {
+ initialConfig?: string;
+ onClose(): void;
+ onApply(tempConfig?: string): void;
+ isOpen: boolean;
+}
+
+export const UpdateModal = ({onClose, onApply, isOpen, initialConfig}: UpdateModalProps) => {
+ const [tempConfig, setTempConfig] = React.useState(initialConfig || '');
+
+ React.useEffect(() => {
+ if (isOpen && initialConfig) {
+ setTempConfig(initialConfig);
+ }
+ }, [isOpen, initialConfig]);
+
+ const handleApply = () => {
+ onApply(tempConfig);
+ };
+ return (
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/editor-v2/containers/Tree/DragContext.scss b/src/editor-v2/containers/Tree/DragContext.scss
new file mode 100644
index 000000000..19f078650
--- /dev/null
+++ b/src/editor-v2/containers/Tree/DragContext.scss
@@ -0,0 +1,31 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}preview';
+
+#{$block} {
+ position: absolute;
+ height: auto;
+ background-color: var(--g-color-text-hint);
+ border: 1px dashed var(--g-color-text-primary);
+ border-radius: 4px;
+ padding: 8px;
+ z-index: 1000;
+ pointer-events: none;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+
+ &-content {
+ display: flex;
+ flex-direction: column;
+ }
+
+ &-type {
+ color: var(--g-color-text-dark-secondary);
+ font-size: 12px;
+ }
+
+ &-title {
+ @include overflow-ellipsis();
+ font-size: 14px;
+ }
+}
diff --git a/src/editor-v2/containers/Tree/DragContext.tsx b/src/editor-v2/containers/Tree/DragContext.tsx
new file mode 100644
index 000000000..703126f31
--- /dev/null
+++ b/src/editor-v2/containers/Tree/DragContext.tsx
@@ -0,0 +1,72 @@
+import * as React from 'react';
+import {editorCn} from '../../utils/cn';
+
+import './DragContext.scss';
+
+const b = editorCn('preview');
+
+export interface DragItem {
+ path: number[];
+ type: string;
+ treeTitle?: string;
+}
+
+// Context for drag and drop operations
+export const DragContext = React.createContext<{
+ draggedItem: DragItem | null;
+ setDraggedItem: React.Dispatch>;
+ previewRef: React.RefObject;
+ showPreview: (rect: DOMRect, item: DragItem) => void;
+ hidePreview: () => void;
+}>({
+ draggedItem: null,
+ setDraggedItem: () => {},
+ previewRef: {current: null},
+ showPreview: () => {},
+ hidePreview: () => {},
+});
+
+// Component for the drag preview
+const DragPreview = React.forwardRef((_, ref) => {
+ return
;
+});
+
+DragPreview.displayName = 'DragPreview';
+
+// Provider component for drag context
+export const DragContextProvider: React.FC> = ({children}) => {
+ const [draggedItem, setDraggedItem] = React.useState(null);
+ const previewRef = React.useRef(null);
+
+ const showPreview = React.useCallback((rect: DOMRect, item: DragItem) => {
+ if (!previewRef.current) return;
+
+ const previewEl = previewRef.current;
+ previewEl.style.display = 'block';
+ previewEl.style.width = `${rect.width}px`;
+ previewEl.style.top = `${rect.bottom}px`;
+ previewEl.style.left = `${rect.left}px`;
+
+ previewEl.innerHTML = `
+
+
${item.type}
+ ${item.treeTitle ? `
${item.treeTitle}
` : ''}
+
+ `;
+ }, []);
+
+ const hidePreview = React.useCallback(() => {
+ if (previewRef.current) {
+ previewRef.current.style.display = 'none';
+ }
+ }, []);
+
+ return (
+
+ {children}
+
+
+ );
+};
diff --git a/src/editor-v2/containers/Tree/Tree.scss b/src/editor-v2/containers/Tree/Tree.scss
new file mode 100644
index 000000000..547de744b
--- /dev/null
+++ b/src/editor-v2/containers/Tree/Tree.scss
@@ -0,0 +1,16 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}tree';
+
+#{$block} {
+ padding: 12px;
+ position: relative;
+
+ &__head {
+ margin-bottom: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+}
diff --git a/src/editor-v2/containers/Tree/Tree.tsx b/src/editor-v2/containers/Tree/Tree.tsx
new file mode 100644
index 000000000..2cbf020a1
--- /dev/null
+++ b/src/editor-v2/containers/Tree/Tree.tsx
@@ -0,0 +1,72 @@
+import * as React from 'react';
+
+import {editorCn} from '../../utils/cn';
+import {DragContextProvider} from './DragContext';
+import {TreeContent, TreeItem} from './TreeContent';
+
+import {useMainEditorStore} from '../../hooks/useMainEditorStore';
+import {Button, Icon} from '@gravity-ui/uikit';
+import {TrashBin} from '@gravity-ui/icons';
+import {generateChildrenPathFromArray, getItemTitle} from '../../utils';
+import {ClassNameProps} from '../../../models';
+
+import './Tree.scss';
+
+const b = editorCn('tree');
+
+const generateTree = (items: TreeItem[]): TreeItem[] => {
+ return items.map((item) => {
+ let children;
+
+ if ('children' in item && item.children?.length) {
+ children = generateTree(item.children);
+ }
+
+ return {
+ type: item.type,
+ children,
+ treeTitle: getItemTitle(item),
+ };
+ });
+};
+
+interface TreeProps extends ClassNameProps {}
+
+const Tree = ({className}: TreeProps) => {
+ const {
+ content,
+ resetBlocks,
+ selectedBlock,
+ duplicateBlock,
+ deleteBlock,
+ setSelectedBlock,
+ reorderBlock,
+ } = useMainEditorStore();
+
+ const selectedBlockPath = React.useMemo(() => {
+ return generateChildrenPathFromArray(selectedBlock || []);
+ }, [selectedBlock]);
+
+ return (
+
+
+
+ resetBlocks()}>
+
+ Clear all
+
+
+
+
+
+ );
+};
+
+export default Tree;
diff --git a/src/editor-v2/containers/Tree/TreeContent.scss b/src/editor-v2/containers/Tree/TreeContent.scss
new file mode 100644
index 000000000..5b724500a
--- /dev/null
+++ b/src/editor-v2/containers/Tree/TreeContent.scss
@@ -0,0 +1,19 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}tree-content';
+
+#{$block} {
+ position: relative;
+
+ &__drop-zone {
+ height: 8px;
+ margin-bottom: 8px;
+ border-radius: 4px;
+ transition: background-color 0.2s;
+ }
+
+ &__children {
+ margin-top: 8px;
+ }
+}
diff --git a/src/editor-v2/containers/Tree/TreeContent.tsx b/src/editor-v2/containers/Tree/TreeContent.tsx
new file mode 100644
index 000000000..afa07066f
--- /dev/null
+++ b/src/editor-v2/containers/Tree/TreeContent.tsx
@@ -0,0 +1,123 @@
+import * as React from 'react';
+
+import {generateChildrenPathFromArray} from '../../utils';
+import {DragContext, DragItem} from './DragContext';
+import {Item} from './TreeItem';
+import {editorCn} from '../../utils/cn';
+
+import './TreeContent.scss';
+
+const b = editorCn('tree-content');
+
+interface TreeContentProps {
+ blockTree: TreeItem[];
+ selectedBlockPath: string;
+ reorderBlock(
+ selectedBlock: number[],
+ destination: number[],
+ position?: 'prepend' | 'append',
+ ): void;
+ onCopy(path: number[]): void;
+ onDelete(path: number[]): void;
+ onSelect(path: number[]): void;
+}
+
+export type TreeItem = {
+ type: string;
+ children?: TreeItem[];
+ treeTitle?: string;
+};
+
+export const TreeContent = ({
+ blockTree,
+ selectedBlockPath,
+ reorderBlock,
+ onCopy,
+ onDelete,
+ onSelect,
+}: React.PropsWithChildren) => {
+ const {draggedItem, setDraggedItem, hidePreview, showPreview} = React.useContext(DragContext);
+
+ const handleFirstPositionDrop = React.useCallback(
+ (e: React.DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ try {
+ const data = e.dataTransfer.getData('application/json');
+ if (data) {
+ const dragItem: DragItem = JSON.parse(data);
+ // Reorder to the first position by using [0] as destination
+ // and 'prepend' as position
+ reorderBlock(dragItem.path, [0], 'prepend');
+
+ // Reset drag context
+ setDraggedItem(null);
+ hidePreview();
+ }
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error('Error parsing drag data:', error);
+ }
+ },
+ [reorderBlock, hidePreview, setDraggedItem],
+ );
+
+ const handleDragOver = React.useCallback(
+ (e: React.DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign
+ e.dataTransfer.dropEffect = 'move';
+
+ // Show preview element at this position
+ const rect = e.currentTarget.getBoundingClientRect();
+ if (draggedItem) {
+ showPreview(rect, draggedItem);
+ }
+ },
+ [draggedItem, showPreview],
+ );
+
+ const renderTree = (items: TreeItem[], parentPathArray?: number[]) => {
+ return items.map(({type, treeTitle, children}, index) => {
+ let blockPathArray: number[];
+ if (parentPathArray) {
+ blockPathArray = [...parentPathArray, index];
+ } else {
+ blockPathArray = [index];
+ }
+ const blockPath = generateChildrenPathFromArray(blockPathArray);
+
+ return (
+ -
+ {children && (
+
{renderTree(children, blockPathArray)}
+ )}
+
+ );
+ });
+ };
+
+ return (
+
+
+
{renderTree(blockTree)}
+
+ );
+};
diff --git a/src/editor-v2/containers/Tree/TreeItem.scss b/src/editor-v2/containers/Tree/TreeItem.scss
new file mode 100644
index 000000000..da2f2d6db
--- /dev/null
+++ b/src/editor-v2/containers/Tree/TreeItem.scss
@@ -0,0 +1,66 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}tree-item';
+
+#{$block} {
+ padding: 8px;
+ margin-bottom: 8px;
+ cursor: pointer;
+
+ &_selected {
+ border: 1.5px var(--g-color-line-brand) solid;
+ }
+
+ &_dragging {
+ opacity: 0.5;
+ }
+
+ &_drag-over {
+ border: 1.5px var(--g-color-line-generic) dashed;
+ }
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ &__main {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ &:hover {
+ #{$block}__buttons {
+ display: flex;
+ }
+ }
+ }
+
+ &__text {
+ flex: 1 1 auto;
+ min-width: 1px;
+ }
+
+ &__buttons {
+ flex: 0 0 auto;
+ flex-direction: row;
+ gap: 4px;
+ display: none;
+ }
+
+ &__type {
+ color: var(--g-color-text-secondary);
+ }
+
+ &__title {
+ @include overflow-ellipsis();
+ }
+
+ &__children-drop-zone {
+ height: 8px;
+ margin-top: 8px;
+ margin-bottom: 0;
+ border-radius: 4px;
+ transition: background-color 0.2s;
+ }
+}
diff --git a/src/editor-v2/containers/Tree/TreeItem.tsx b/src/editor-v2/containers/Tree/TreeItem.tsx
new file mode 100644
index 000000000..0aafed007
--- /dev/null
+++ b/src/editor-v2/containers/Tree/TreeItem.tsx
@@ -0,0 +1,242 @@
+import {Copy, TrashBin} from '@gravity-ui/icons';
+import {Button, Card, Icon} from '@gravity-ui/uikit';
+import * as React from 'react';
+
+import {editorCn} from '../../utils/cn';
+import {DragContext, DragItem} from './DragContext';
+
+import './TreeItem.scss';
+
+const b = editorCn('tree-item');
+
+export interface ItemProps {
+ type: string;
+ treeTitle?: string;
+ path: number[];
+ selected: boolean;
+ onCopy(path: number[]): void;
+ onDelete(path: number[]): void;
+ onSelect(path: number[]): void;
+ onReorder(
+ sourcePath: number[],
+ destinationPath: number[],
+ position?: 'prepend' | 'append',
+ ): void;
+ children?: React.ReactNode;
+}
+
+export const Item = ({
+ type,
+ children,
+ treeTitle,
+ path,
+ selected,
+ onCopy,
+ onDelete,
+ onSelect,
+ onReorder,
+}: ItemProps) => {
+ const {draggedItem, setDraggedItem, showPreview, hidePreview} = React.useContext(DragContext);
+ const [isDragging, setIsDragging] = React.useState(false);
+ const [isDragOver, setIsDragOver] = React.useState(false);
+ const [mouseDownPos, setMouseDownPos] = React.useState<{x: number; y: number} | null>(null);
+ const itemRef = React.useRef(null);
+
+ // Scroll into view when selected
+ React.useEffect(() => {
+ if (selected && itemRef.current) {
+ itemRef.current.scrollIntoView({
+ behavior: 'smooth',
+ block: 'nearest',
+ });
+ }
+ }, [selected]);
+
+ const handleCopy = React.useCallback(() => {
+ onCopy(path);
+ }, [onCopy, path]);
+
+ const handleDelete = React.useCallback(() => {
+ onDelete(path);
+ }, [onDelete, path]);
+
+ const handleMouseDown = React.useCallback((e: React.MouseEvent) => {
+ setMouseDownPos({x: e.clientX, y: e.clientY});
+ }, []);
+
+ const handleMouseUp = React.useCallback(
+ (e: React.MouseEvent) => {
+ if (mouseDownPos) {
+ // Check if the mouse has moved significantly (dragging) or just a click
+ const dx = Math.abs(e.clientX - mouseDownPos.x);
+ const dy = Math.abs(e.clientY - mouseDownPos.y);
+
+ // If the mouse hasn't moved much, consider it a click for selection
+ if (dx < 5 && dy < 5) {
+ e.stopPropagation();
+ onSelect(path);
+ }
+ }
+ setMouseDownPos(null);
+ },
+ [mouseDownPos, onSelect, path],
+ );
+
+ const handleDragStart = React.useCallback(
+ (e: React.DragEvent) => {
+ e.stopPropagation();
+ const dragData: DragItem = {
+ path,
+ type,
+ treeTitle,
+ };
+ e.dataTransfer.setData('application/json', JSON.stringify(dragData));
+ // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign
+ e.dataTransfer.effectAllowed = 'move';
+ setIsDragging(true);
+
+ // Update drag context
+ setDraggedItem(dragData);
+ },
+ [path, type, treeTitle, setDraggedItem],
+ );
+
+ const handleDragEnd = React.useCallback(() => {
+ setIsDragging(false);
+
+ // Reset drag context
+ setDraggedItem(null);
+ hidePreview();
+ }, [setDraggedItem, hidePreview]);
+
+ const handleDragOver = React.useCallback(
+ (e: React.DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign
+ e.dataTransfer.dropEffect = 'move';
+ setIsDragOver(true);
+
+ const rect = e.currentTarget.getBoundingClientRect();
+ if (draggedItem) {
+ showPreview(rect, draggedItem);
+ }
+ },
+ [showPreview, draggedItem],
+ );
+
+ const handleDragLeave = React.useCallback(() => {
+ setIsDragOver(false);
+ hidePreview();
+ }, [hidePreview]);
+
+ const handleDrop = React.useCallback(
+ (e: React.DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setIsDragOver(false);
+
+ try {
+ const data = e.dataTransfer.getData('application/json');
+ if (data) {
+ const dragItem: DragItem = JSON.parse(data);
+ if (dragItem.path.join(',') !== path.join(',')) {
+ onReorder(dragItem.path, path);
+
+ // Reset drag context
+ setDraggedItem(null);
+ hidePreview();
+ }
+ }
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error('Error parsing drag data:', error);
+ }
+ },
+ [onReorder, path, setDraggedItem, hidePreview],
+ );
+
+ const handleChildrenFirstPositionDrop = React.useCallback(
+ (e: React.DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ try {
+ const data = e.dataTransfer.getData('application/json');
+ if (data) {
+ const dragItem: DragItem = JSON.parse(data);
+ // Create a path for the first child position
+ const firstChildPath = [...path, 0];
+ // Reorder to the first position within children
+ onReorder(dragItem.path, firstChildPath, 'prepend');
+
+ // Reset drag context
+ setDraggedItem(null);
+ hidePreview();
+ }
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error('Error parsing drag data:', error);
+ }
+ },
+ [onReorder, path, setDraggedItem, hidePreview],
+ );
+
+ const handleDropZoneDragOver = (e: React.DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign
+ e.dataTransfer.dropEffect = 'move';
+
+ // Show preview element at this position
+ const rect = e.currentTarget.getBoundingClientRect();
+ if (draggedItem) {
+ showPreview(rect, draggedItem);
+ }
+ };
+
+ return (
+ }
+ onMouseUp={handleMouseUp as unknown as React.MouseEventHandler<'div'>}
+ draggable
+ onDragStart={handleDragStart as unknown as React.DragEventHandler<'div'>}
+ onDragEnd={handleDragEnd}
+ onDragOver={handleDragOver as unknown as React.DragEventHandler<'div'>}
+ onDragLeave={handleDragLeave}
+ onDrop={handleDrop as unknown as React.DragEventHandler<'div'>}
+ >
+
+ {children && (
+
+
+ {children}
+
+ )}
+
+ );
+};
diff --git a/src/editor-v2/containers/Tree/index.ts b/src/editor-v2/containers/Tree/index.ts
new file mode 100644
index 000000000..f5143b3bd
--- /dev/null
+++ b/src/editor-v2/containers/Tree/index.ts
@@ -0,0 +1,4 @@
+export {default} from './Tree';
+export * from './DragContext';
+export * from './TreeItem';
+export * from './TreeContent';
diff --git a/src/editor-v2/containers/ViewSwitches/ViewSwitches.scss b/src/editor-v2/containers/ViewSwitches/ViewSwitches.scss
new file mode 100644
index 000000000..a6e4d7a65
--- /dev/null
+++ b/src/editor-v2/containers/ViewSwitches/ViewSwitches.scss
@@ -0,0 +1,20 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns}view-switches';
+
+#{$block} {
+ padding: 12px;
+ display: inline-flex;
+ align-items: center;
+ gap: 12px;
+
+ &__zoom {
+ display: flex;
+ gap: 4px;
+
+ &-select {
+ min-width: 80px;
+ }
+ }
+}
diff --git a/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx b/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx
new file mode 100644
index 000000000..a5751b642
--- /dev/null
+++ b/src/editor-v2/containers/ViewSwitches/ViewSwitches.tsx
@@ -0,0 +1,140 @@
+import * as React from 'react';
+import {Display, Minus, Plus, Smartphone, SquareDashed} from '@gravity-ui/icons';
+import {Button, Icon, SegmentedRadioGroup, Select} from '@gravity-ui/uikit';
+
+import {ZOOM_STEPS} from '../../constants';
+import {useMainEditorStore} from '../../hooks/useMainEditorStore';
+import {editorCn} from '../../utils/cn';
+
+import './ViewSwitches.scss';
+
+const b = editorCn('view-switches');
+
+/**
+ * Device option type definition
+ */
+interface DeviceOption {
+ /** React node to display as the option label */
+ label: React.ReactNode;
+ /** Device width value (e.g., '100%', '768px') */
+ value: string;
+ /** Descriptive name for accessibility */
+ ariaLabel: string;
+}
+
+/**
+ * Available device viewport options
+ * - Desktop: 100% width
+ * - Tablet: 768px width
+ * - Mobile: 375px width
+ */
+const DEVICE_OPTIONS: DeviceOption[] = [
+ {
+ label: ,
+ value: '100%',
+ ariaLabel: 'Desktop view',
+ },
+ {
+ label: ,
+ value: '768px',
+ ariaLabel: 'Tablet view',
+ },
+ {
+ label: ,
+ value: '375px',
+ ariaLabel: 'Mobile view',
+ },
+];
+
+const ViewSwitches: React.FC = () => {
+ const {
+ zoom,
+ setZoom,
+ decreaseZoom,
+ increaseZoom,
+ deviceWidth,
+ setDeviceWidth,
+ togglePreviewMode,
+ } = useMainEditorStore();
+
+ // Memoize zoom options to prevent unnecessary recalculations
+ const zoomOptions = React.useMemo(
+ () =>
+ ZOOM_STEPS.map((step) => ({
+ value: String(step),
+ content: `${step}%`,
+ })),
+ [],
+ );
+
+ // Memoize current zoom value for Select component
+ const currentZoomValue = React.useMemo(() => [String(zoom)], [zoom]);
+
+ // Create stable callback for zoom updates
+ const handleZoomUpdate = React.useCallback(
+ (value: string | string[]) => {
+ const newZoom = Number(Array.isArray(value) ? value[0] : value);
+ if (!isNaN(newZoom) && ZOOM_STEPS.includes(newZoom)) {
+ setZoom(newZoom);
+ }
+ },
+ [setZoom],
+ );
+
+ return (
+
+
+ {DEVICE_OPTIONS.map(({value, label, ariaLabel}) => (
+
+ {label}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = Math.max(...ZOOM_STEPS)}
+ >
+
+
+
+
+ );
+};
+
+export default React.memo(ViewSwitches);
diff --git a/src/editor-v2/containers/__stories__/Editor.stories.tsx b/src/editor-v2/containers/__stories__/Editor.stories.tsx
new file mode 100644
index 000000000..f06c3746b
--- /dev/null
+++ b/src/editor-v2/containers/__stories__/Editor.stories.tsx
@@ -0,0 +1,20 @@
+import {Meta, StoryFn} from '@storybook/react';
+
+import {Editor} from '../Editor/Editor';
+
+export default {
+ title: 'Editor/Main 2.0',
+ component: Editor,
+} as Meta;
+
+const DefaultTemplate: StoryFn = (args) => {
+ return (
+
+
+
+ );
+};
+
+export const Default = DefaultTemplate.bind({});
+
+// Default.args = data.default;
diff --git a/src/editor-v2/containers/__stories__/utils.ts b/src/editor-v2/containers/__stories__/utils.ts
new file mode 100644
index 000000000..7d9be4882
--- /dev/null
+++ b/src/editor-v2/containers/__stories__/utils.ts
@@ -0,0 +1,22 @@
+import isEqual from 'lodash/isEqual';
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export const memoizeLast = (fn: (...args: any[]) => any) => {
+ let cacheKey: Parameters;
+ let cacheResult: ReturnType;
+
+ return (...params: Parameters) => {
+ if (!isEqual(params, cacheKey)) {
+ try {
+ const result = fn(...params);
+
+ cacheResult = result;
+ cacheKey = params;
+ } catch {
+ return cacheResult;
+ }
+ }
+
+ return cacheResult;
+ };
+};
diff --git a/src/editor-v2/containers/index.ts b/src/editor-v2/containers/index.ts
new file mode 100644
index 000000000..fc0241a72
--- /dev/null
+++ b/src/editor-v2/containers/index.ts
@@ -0,0 +1,7 @@
+export {Editor} from './Editor/Editor';
+export {default as BlockConfigForm} from './BlockConfigForm/BlockConfigForm';
+export {default as BlocksList} from './BlocksList/BlocksList';
+export {default as Source} from './Source/Source';
+export {default as SourceCode} from './SourceCode/SourceCode';
+export {default as Tree} from './Tree/Tree';
+export {default as ViewSwitches} from './ViewSwitches/ViewSwitches';
diff --git a/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx b/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx
new file mode 100644
index 000000000..1332029b7
--- /dev/null
+++ b/src/editor-v2/context/editorStore/MainEditorStoreContext.tsx
@@ -0,0 +1,12 @@
+import * as React from 'react';
+import {StoreApi} from 'zustand';
+
+import {EditorStore, createEditorStore} from '../../store';
+
+export interface MainEditorStoreContextProps {
+ state: StoreApi;
+}
+
+export const MainEditorStoreContext = React.createContext({
+ state: createEditorStore(),
+});
diff --git a/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx b/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx
new file mode 100644
index 000000000..e290b9880
--- /dev/null
+++ b/src/editor-v2/context/editorStore/MainEditorStoreProvider.tsx
@@ -0,0 +1,50 @@
+import * as React from 'react';
+import {StoreApi} from 'zustand';
+
+import {EditorState} from '../../../common/store';
+import {StoreSyncMessage} from '../../../common/types';
+import {removeFn} from '../../../common/utils';
+import {EditorStore, createEditorStore} from '../../store';
+import {IframeContext} from '../iframeContext';
+
+import {MainEditorStoreContext} from './MainEditorStoreContext';
+
+interface MainEditorProviderProps extends React.PropsWithChildren {}
+
+export const MainEditorStoreProvider = ({children}: MainEditorProviderProps) => {
+ const {iframeElement} = React.useContext(IframeContext);
+ const storeRef = React.useRef>();
+
+ const sendPostMessage = React.useCallback(
+ (data: EditorState) => {
+ const message: StoreSyncMessage = {
+ state: data,
+ };
+
+ if (iframeElement && iframeElement.contentWindow) {
+ iframeElement.contentWindow.postMessage(message, '*');
+ }
+ },
+ [iframeElement],
+ );
+
+ if (!storeRef.current) {
+ storeRef.current = createEditorStore();
+ }
+
+ React.useEffect(() => {
+ storeRef.current?.subscribe((state) => {
+ sendPostMessage(removeFn(state));
+ });
+ }, [sendPostMessage]);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/editor-v2/context/editorStore/index.ts b/src/editor-v2/context/editorStore/index.ts
new file mode 100644
index 000000000..7804dc9a1
--- /dev/null
+++ b/src/editor-v2/context/editorStore/index.ts
@@ -0,0 +1,2 @@
+export * from './MainEditorStoreContext';
+export * from './MainEditorStoreProvider';
diff --git a/src/editor-v2/context/iframeContext/IframeContext.tsx b/src/editor-v2/context/iframeContext/IframeContext.tsx
new file mode 100644
index 000000000..c8962762f
--- /dev/null
+++ b/src/editor-v2/context/iframeContext/IframeContext.tsx
@@ -0,0 +1,19 @@
+/**
+ * Context for iframe window
+ **/
+
+import * as React from 'react';
+
+export interface IframeContextProps {
+ iframeElement?: HTMLIFrameElement;
+ setIframeElement: (element: HTMLIFrameElement) => void;
+ url: string;
+ setUrl: (url: string) => void;
+ disableUrlField?: boolean;
+}
+
+export const IframeContext = React.createContext({
+ setIframeElement: () => {},
+ setUrl: () => {},
+ url: '',
+});
diff --git a/src/editor-v2/context/iframeContext/IframeProvider.tsx b/src/editor-v2/context/iframeContext/IframeProvider.tsx
new file mode 100644
index 000000000..499850d21
--- /dev/null
+++ b/src/editor-v2/context/iframeContext/IframeProvider.tsx
@@ -0,0 +1,33 @@
+import * as React from 'react';
+
+import {IframeContext} from './IframeContext';
+
+interface IframeProviderProps extends React.PropsWithChildren {
+ initialUrl?: string;
+ disableUrlField?: boolean;
+}
+
+export const IframeProvider = ({
+ children,
+ initialUrl = '',
+ disableUrlField,
+}: IframeProviderProps) => {
+ const [iframeElement, setIframeElement] = React.useState();
+ const [url, setUrl] = React.useState(initialUrl);
+
+ const setIframeElementFunc = (element: HTMLIFrameElement) => setIframeElement(element);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/editor-v2/context/iframeContext/index.ts b/src/editor-v2/context/iframeContext/index.ts
new file mode 100644
index 000000000..dc2795f0f
--- /dev/null
+++ b/src/editor-v2/context/iframeContext/index.ts
@@ -0,0 +1,2 @@
+export * from './IframeContext';
+export * from './IframeProvider';
diff --git a/src/editor-v2/hooks/index.ts b/src/editor-v2/hooks/index.ts
new file mode 100644
index 000000000..3b3541883
--- /dev/null
+++ b/src/editor-v2/hooks/index.ts
@@ -0,0 +1,2 @@
+export {usePostMessageEvents} from './usePostMessageEvents';
+export {useMainEditorStore} from './useMainEditorStore';
diff --git a/src/editor-v2/hooks/useEditorTabs.tsx b/src/editor-v2/hooks/useEditorTabs.tsx
new file mode 100644
index 000000000..e85051065
--- /dev/null
+++ b/src/editor-v2/hooks/useEditorTabs.tsx
@@ -0,0 +1,93 @@
+import * as React from 'react';
+
+import Tabs, {TabItemProps} from '../components/Tabs/Tabs';
+import BlockConfigForm from '../containers/BlockConfigForm/BlockConfigForm';
+import BlocksList from '../containers/BlocksList/BlocksList';
+import GlobalConfig from '../containers/GlobalConfig/GlobalConfig';
+import SourceCode from '../containers/SourceCode/SourceCode';
+import Tree from '../containers/Tree';
+
+export const useEditorTabs = ({
+ leftTabs,
+ rightTabs,
+}: {
+ leftTabs?: TabItemProps[];
+ rightTabs?: TabItemProps[];
+}) => {
+ const tabs = React.useMemo(
+ () => ({
+ left: [
+ {
+ id: 'page',
+ title: 'PAGE',
+ component: () => (
+ (
+
+ ),
+ withPadding: true,
+ },
+ ]}
+ />
+ ),
+ },
+ {
+ id: 'global',
+ title: 'GLOBAL',
+ component: GlobalConfig,
+ withPadding: true,
+ },
+ ...(leftTabs || []),
+ ],
+ right: [
+ {
+ id: 'edit',
+ title: 'EDIT',
+ component: () => (
+ (
+
+ ),
+ withPadding: true,
+ },
+ ]}
+ />
+ ),
+ },
+ ...(rightTabs || []),
+ ],
+ }),
+ [leftTabs, rightTabs],
+ );
+
+ return tabs;
+};
diff --git a/src/editor-v2/hooks/useMainEditorInitialize.ts b/src/editor-v2/hooks/useMainEditorInitialize.ts
new file mode 100644
index 000000000..ae00f0123
--- /dev/null
+++ b/src/editor-v2/hooks/useMainEditorInitialize.ts
@@ -0,0 +1,73 @@
+import * as React from 'react';
+import {usePostMessageAPIListener} from '../../common/postMessage';
+import {PageContentWithNavigation} from '../../models';
+
+import {useMainEditorStore} from './useMainEditorStore';
+import {usePostMessageEvents} from './usePostMessageEvents';
+
+const useMainEditorInitialize = (initialContent?: PageContentWithNavigation) => {
+ const {requestPostMessage} = usePostMessageEvents();
+ const {
+ initialize,
+ setConfig,
+ setContent,
+ manipulateOverlayMode,
+ disableMode,
+ insertBlock,
+ reorderBlock,
+ preInsertBlockType,
+ preReorderBlockPath,
+ } = useMainEditorStore();
+
+ usePostMessageAPIListener(
+ 'ON_INIT',
+ () => {
+ initialize();
+ requestPostMessage('GET_SUPPORTED_BLOCKS', {});
+
+ if (!initialContent) {
+ requestPostMessage('GET_INITIAL_CONTENT', {});
+ }
+ },
+ [requestPostMessage],
+ );
+
+ React.useEffect(() => {
+ if (initialContent) {
+ setContent(initialContent);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [initialContent]);
+
+ usePostMessageAPIListener('ON_INITIAL_CONTENT', (data) => {
+ setContent(data);
+ });
+
+ usePostMessageAPIListener('ON_SUPPORTED_BLOCKS', (data) => {
+ setConfig(data);
+ });
+
+ usePostMessageAPIListener(
+ 'ON_MOUSE_UP',
+ ({path, position}) => {
+ if (manipulateOverlayMode === 'insert' && path && position && preInsertBlockType) {
+ insertBlock(
+ path,
+ preInsertBlockType,
+ ['left', 'top'].includes(position) ? 'prepend' : 'append',
+ );
+ }
+ if (manipulateOverlayMode === 'reorder' && path && position && preReorderBlockPath) {
+ reorderBlock(
+ preReorderBlockPath,
+ path,
+ ['left', 'top'].includes(position) ? 'prepend' : 'append',
+ );
+ }
+ disableMode();
+ },
+ [preInsertBlockType, preReorderBlockPath],
+ );
+};
+
+export default useMainEditorInitialize;
diff --git a/src/editor-v2/hooks/useMainEditorStore.ts b/src/editor-v2/hooks/useMainEditorStore.ts
new file mode 100644
index 000000000..541ff6540
--- /dev/null
+++ b/src/editor-v2/hooks/useMainEditorStore.ts
@@ -0,0 +1,9 @@
+import * as React from 'react';
+import {useStore} from 'zustand';
+
+import {MainEditorStoreContext} from '../context/editorStore';
+
+export const useMainEditorStore = () => {
+ const {state} = React.useContext(MainEditorStoreContext);
+ return useStore(state);
+};
diff --git a/src/editor-v2/hooks/usePostMessageEvents.ts b/src/editor-v2/hooks/usePostMessageEvents.ts
new file mode 100644
index 000000000..cc8000843
--- /dev/null
+++ b/src/editor-v2/hooks/usePostMessageEvents.ts
@@ -0,0 +1,26 @@
+import * as React from 'react';
+
+import {requestActionPostMessage} from '../../common/postMessage';
+import {ActionMessageTypes} from '../../common/types';
+import {IframeContext} from '../context/iframeContext';
+
+interface UsePostMessageRequestReturn {
+ requestPostMessage: (
+ action: K,
+ data: ActionMessageTypes[K],
+ ) => void;
+}
+
+export function usePostMessageEvents(): UsePostMessageRequestReturn {
+ const {iframeElement} = React.useContext(IframeContext);
+
+ return {
+ requestPostMessage: (action, data) => {
+ if (iframeElement && iframeElement.contentWindow) {
+ return requestActionPostMessage(action, data, iframeElement.contentWindow);
+ }
+
+ return undefined;
+ },
+ };
+}
diff --git a/src/editor-v2/index.ts b/src/editor-v2/index.ts
new file mode 100644
index 000000000..346f94855
--- /dev/null
+++ b/src/editor-v2/index.ts
@@ -0,0 +1,5 @@
+export * from '../common/types';
+export * from './containers';
+export * from './hooks';
+export * from './utils';
+export * from './constants';
diff --git a/src/editor-v2/store.ts b/src/editor-v2/store.ts
new file mode 100644
index 000000000..0c8dd5b89
--- /dev/null
+++ b/src/editor-v2/store.ts
@@ -0,0 +1,255 @@
+import _ from 'lodash';
+
+import {EditorState, initialStore} from '../common/store';
+import {DynamicFormValue} from '../form-generator';
+import {initializeStore} from '../common/utils';
+import {ConstructorBlock, PageContentWithNavigation} from '../models';
+
+import {ZOOM_STEPS} from './constants';
+import {
+ duplicateArrayItem,
+ generateChildrenPathFromArray,
+ getDestinationShiftBeforeReorder,
+ insert,
+ isItemsNeighbours,
+ modifyObjectByPath,
+ removeFromArray,
+ reorderArrayItems,
+} from './utils';
+
+export interface EditorMethods {
+ initialize(): void;
+ setSelectedBlock(path: number[] | null): void;
+ setHeight(height: number): void;
+ setDeviceWidth(deviceWidth: string): void;
+ setZoom(zoom: number): void;
+ increaseZoom(): void;
+ decreaseZoom(): void;
+ togglePreviewMode(): void;
+ setConfig(data: Pick): void;
+ setContent(data: PageContentWithNavigation): void;
+ insertBlock(path: number[], blockType: string, position?: 'prepend' | 'append'): void;
+ enableInsertMode(blockType: string): void;
+ enableReorderMode(path: number[]): void;
+ disableMode(): void;
+ updateField(path: string, value: DynamicFormValue): void;
+ deleteBlock(path: number[]): void;
+ duplicateBlock(path: number[]): void;
+ reorderBlock(path: number[], destination: number[], position?: 'prepend' | 'append'): void;
+ resetInitialize(): void;
+ resetBlocks(): void;
+}
+
+export type EditorStore = EditorState & EditorMethods;
+
+export const createEditorStore = initializeStore(
+ initialStore,
+ (set, get) => ({
+ setHeight(height: number) {
+ // We have to add 200-500px, because of bottom padding or margin of last element
+ // which is not taken into calculation of final height
+ const newHeight = height + 500;
+ set((state) => ({...state, height: newHeight}));
+ },
+ setDeviceWidth(deviceWidth: string) {
+ set((state) => ({...state, deviceWidth}));
+ },
+ setZoom(zoom) {
+ if (zoom > 0) {
+ set((state) => ({...state, zoom}));
+ }
+ },
+ increaseZoom() {
+ const currentZoom = get().zoom;
+
+ for (const step of ZOOM_STEPS) {
+ if (currentZoom < step) {
+ get().setZoom(step);
+ break;
+ }
+ }
+ },
+ decreaseZoom() {
+ const currentZoom = get().zoom;
+ const reverseSteps = ZOOM_STEPS.slice().reverse();
+
+ for (const step of reverseSteps) {
+ if (currentZoom > step) {
+ get().setZoom(step);
+ break;
+ }
+ }
+ },
+ togglePreviewMode() {
+ set((state) => ({...state, isPreviewMode: !state.isPreviewMode}));
+ },
+ setConfig(data) {
+ set((state) => ({...state, ...data}));
+ },
+ insertBlock: (arrayPath, blockType, position = 'append') => {
+ if (position === 'append') {
+ // TODO: fix
+ // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign
+ arrayPath[arrayPath.length - 1] = arrayPath[arrayPath.length - 1] + 1;
+ }
+
+ const blocksConfig = get().content.blocks;
+ const blocksData = get().blocks;
+
+ const foundBlock = blocksData.find(({type}) => type === blockType);
+ const defaultValue =
+ foundBlock && foundBlock.schema.default
+ ? {...foundBlock.schema.default, type: blockType}
+ : {type: blockType};
+
+ const newBlocksConfig = modifyObjectByPath(
+ blocksConfig,
+ arrayPath,
+ (parentBlocks, index) =>
+ insert(parentBlocks, index, defaultValue as ConstructorBlock),
+ );
+
+ set((state) => ({
+ ...state,
+ content: {...state.content, blocks: newBlocksConfig},
+ selectedBlock: arrayPath,
+ }));
+ },
+ enableInsertMode(blockType: string) {
+ set((state) => ({
+ ...state,
+ manipulateOverlayMode: 'insert',
+ preInsertBlockType: blockType,
+ }));
+ },
+ disableMode() {
+ set((state) => ({
+ ...state,
+ manipulateOverlayMode: false,
+ preInsertBlockType: undefined,
+ preReorderBlockPath: undefined,
+ }));
+ },
+ enableReorderMode(path) {
+ set((state) => ({
+ ...state,
+ manipulateOverlayMode: 'reorder',
+ preReorderBlockPath: path,
+ }));
+ },
+ setContent(content) {
+ set((state) => ({
+ ...state,
+ content: content,
+ }));
+ },
+ initialize() {
+ set((state) => ({
+ ...state,
+ initialized: true,
+ }));
+ },
+ setSelectedBlock(path) {
+ set((state) => ({
+ ...state,
+ selectedBlock: path,
+ }));
+ },
+ updateField(path, value) {
+ set((state) => {
+ const newConfig = _.set(state.content, path, value);
+ return {
+ ...state,
+ content: newConfig,
+ };
+ });
+ },
+ deleteBlock: (arrayPath) => {
+ const blocksConfig = get().content.blocks;
+
+ const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, removeFromArray);
+ set((state) => ({
+ ...state,
+ content: {...state.content, blocks: newBlocksConfig},
+ selectedBlock: null,
+ }));
+ },
+ duplicateBlock: (arrayPath) => {
+ const blocksConfig = get().content.blocks;
+
+ const newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, duplicateArrayItem);
+
+ set((state) => ({
+ ...state,
+ content: {...state.content, blocks: newBlocksConfig},
+ }));
+ },
+ reorderBlock: (arrayPath, destination, position = 'append') => {
+ // Create a copy of the destination array before any modifications
+ let finalDestinationPath: number[] = _.cloneDeep(destination);
+
+ if (position === 'append') {
+ // TODO: fix
+ // eslint-disable-next-line no-not-accumulator-reassign/no-not-accumulator-reassign, no-param-reassign
+ destination[destination.length - 1] = destination[destination.length - 1] + 1;
+ }
+
+ let newBlocksConfig: ConstructorBlock[];
+ const blocksConfig = get().content.blocks;
+ // Copy
+ const copiedBlock = _.get(blocksConfig, generateChildrenPathFromArray(arrayPath));
+
+ if (isItemsNeighbours(arrayPath, destination)) {
+ newBlocksConfig = modifyObjectByPath(blocksConfig, arrayPath, (parentBlocks) => {
+ return reorderArrayItems(
+ parentBlocks,
+ arrayPath[arrayPath.length - 1],
+ destination[destination.length - 1],
+ );
+ });
+
+ if (
+ position === 'append' &&
+ destination[destination.length - 1] < arrayPath[arrayPath.length - 1]
+ ) {
+ finalDestinationPath[finalDestinationPath.length - 1] =
+ finalDestinationPath[finalDestinationPath.length - 1] + 1;
+ }
+ } else {
+ const arrayDest = getDestinationShiftBeforeReorder(arrayPath, destination);
+ finalDestinationPath = _.cloneDeep(arrayDest);
+
+ // Delete
+ const blocksConfigWithoutBlock = modifyObjectByPath(
+ blocksConfig,
+ arrayPath,
+ removeFromArray,
+ );
+ // Paste
+ newBlocksConfig = modifyObjectByPath(
+ blocksConfigWithoutBlock,
+ arrayDest,
+ (parentBlocks, index) => insert(parentBlocks, index, copiedBlock),
+ );
+ }
+
+ set((state) => ({
+ ...state,
+ content: {...state.content, blocks: newBlocksConfig},
+ selectedBlock: finalDestinationPath,
+ }));
+ },
+ resetInitialize: () => {
+ set((state) => ({
+ ...state,
+ initialized: false,
+ }));
+ },
+ resetBlocks: () => {
+ set((state) => ({
+ ...state,
+ content: {...state.content, blocks: []},
+ }));
+ },
+ }),
+);
diff --git a/src/editor-v2/styles/mixins.scss b/src/editor-v2/styles/mixins.scss
new file mode 100644
index 000000000..9b0821fbc
--- /dev/null
+++ b/src/editor-v2/styles/mixins.scss
@@ -0,0 +1,60 @@
+@import '@gravity-ui/uikit/styles/mixins.scss';
+@import './variables.scss';
+
+@mixin control($hoverScale: 1.05) {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ transition: transform $editorTransitionTime;
+
+ &:hover {
+ transform: scale($hoverScale);
+ }
+}
+
+@mixin custom-scrollbar($width: 8px, $track-radius: 4px, $thumb-radius: 4px) {
+ scrollbar-width: thin;
+ scrollbar-color: var(--g-color-scroll-handle) transparent;
+ overflow: auto;
+
+ &::-webkit-scrollbar {
+ width: $width;
+ height: $width;
+ background-color: transparent;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: transparent;
+ border-radius: $track-radius;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: var(--g-color-scroll-handle);
+ border-radius: $thumb-radius;
+ transition: background-color $editorTransitionTime;
+ opacity: 0;
+
+ &:hover {
+ background: var(--g-color-scroll-handle-hover);
+ opacity: 1;
+ }
+
+ &:active {
+ background: var(--g-color-scroll-handle-hover);
+ opacity: 1;
+ }
+ }
+
+ &::-webkit-scrollbar-corner {
+ background: transparent;
+ }
+
+ &:hover::-webkit-scrollbar-thumb {
+ opacity: 0.7;
+ }
+
+ &:not(:hover)::-webkit-scrollbar-thumb {
+ opacity: 0;
+ transition: opacity 0.5s ease-out;
+ }
+}
diff --git a/src/editor-v2/styles/root.scss b/src/editor-v2/styles/root.scss
new file mode 100644
index 000000000..c25a7298a
--- /dev/null
+++ b/src/editor-v2/styles/root.scss
@@ -0,0 +1,9 @@
+.g-root {
+ --g-color-base-selection: var(--g-color-private-black-200);
+ --g-color-base-selection-hover: var(--g-color-private-black-300);
+ --g-color-base-brand: rgb(38, 38, 38); // --g-color-private-black-850-solid light theme only
+ --g-color-base-brand-hover: rgb(76, 76, 76); // --g-color-private-black-700-solid
+ --g-color-text-brand-contrast: var(--g-color-text-light-primary);
+ --g-color-text-brand-heavy: rgb(76, 76, 76);
+ --g-color-line-brand: var(--g-color-text-primary);
+}
diff --git a/src/editor-v2/styles/variables.scss b/src/editor-v2/styles/variables.scss
new file mode 100644
index 000000000..d44cdee40
--- /dev/null
+++ b/src/editor-v2/styles/variables.scss
@@ -0,0 +1,8 @@
+$ns: 'pceditor-';
+$editorTransitionTime: 0.2s;
+$editorShadow:
+ 0px 2px 8px rgba(0, 0, 0, 0.06),
+ 0px 4px 24px rgba(0, 0, 0, 0.06);
+$editorControlBorderRadius: 8px;
+
+$headerHeight: 0px;
diff --git a/src/editor-v2/utils/cn.ts b/src/editor-v2/utils/cn.ts
new file mode 100644
index 000000000..a43775d67
--- /dev/null
+++ b/src/editor-v2/utils/cn.ts
@@ -0,0 +1,5 @@
+import {withNaming} from '@bem-react/classname';
+
+export const EDITOR_NAMESPACE = 'pceditor-';
+
+export const editorCn = withNaming({n: EDITOR_NAMESPACE, e: '__', m: '_'});
diff --git a/src/editor-v2/utils/code.ts b/src/editor-v2/utils/code.ts
new file mode 100644
index 000000000..3b4663267
--- /dev/null
+++ b/src/editor-v2/utils/code.ts
@@ -0,0 +1,12 @@
+import yaml from 'js-yaml';
+
+import {PageContent} from '../../models';
+
+export function parseCode(code: string) {
+ const pageContent = yaml.load(code) as PageContent;
+
+ return {
+ ...pageContent,
+ blocks: pageContent.blocks?.filter(Boolean),
+ };
+}
diff --git a/src/editor-v2/utils/index.ts b/src/editor-v2/utils/index.ts
new file mode 100644
index 000000000..53f16dc71
--- /dev/null
+++ b/src/editor-v2/utils/index.ts
@@ -0,0 +1,179 @@
+import _ from 'lodash';
+
+import {ConstructorBlock} from '../../models';
+export * from './code';
+export * from './cn';
+
+export function insert(arr: Array, index: number, newItem: T) {
+ return [...arr.slice(0, index), newItem, ...arr.slice(index)];
+}
+
+export function removeFromArray(array: Array, index: number) {
+ return [...array.slice(0, index), ...array.slice(index + 1)];
+}
+
+export function swapArrayItems(array: Array, firstIndex: number, secondIndex: number) {
+ const results = array.slice();
+ const firstItem = array[firstIndex];
+ results[firstIndex] = array[secondIndex];
+ results[secondIndex] = firstItem;
+ return results;
+}
+
+export function reorderArrayItems(array: Array, index: number, destination: number) {
+ const min = Math.min(index, destination);
+ const max = Math.max(index, destination);
+ const firstOperationRemove = index < destination;
+ const result = [];
+ result.push(...array.slice(0, min));
+ if (!firstOperationRemove) {
+ result.push(array[index]);
+ }
+ result.push(...array.slice(firstOperationRemove ? min + 1 : min, max));
+ if (firstOperationRemove) {
+ result.push(array[index]);
+ }
+ result.push(...array.slice(firstOperationRemove ? max : max + 1, array.length));
+ return result;
+}
+
+export function duplicateArrayItem(array: Array, index: number) {
+ const duplicatedItem = _.cloneDeep(array[index]);
+ return [...array.slice(0, index), duplicatedItem, ...array.slice(index)];
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export function insertByPath(object: T, path: string, value: any) {
+ if (!path) {
+ return value as T;
+ }
+
+ return _.setWith(_.clone(object), path, value, _.clone);
+}
+
+/*
+ * path: string;
+ * Example:
+ * 1. blocks[0] => {path: blocks, index: 0}
+ * 2. blocks[2].children[10] => {path: blocks[2].children, index: 10}
+ **/
+export function splitPathAndIndex(path: string) {
+ // Match blocks[3], blocks[0].children[12], blocks[0], blocks[999999]
+ const bracketsRegExp = /(.*)\[(\d+)]$/g;
+ const regexpMatches = Array.from(path.matchAll(bracketsRegExp));
+ if (regexpMatches.length) {
+ return {
+ // blocks, blocks[0].children
+ path: regexpMatches[0][1],
+ // 3, 12, 0, 9999
+ index: Number(regexpMatches[0][2]),
+ };
+ }
+
+ // eslint-disable-next-line no-console
+ console.error('Non correct path for splitting');
+ return undefined;
+}
+
+/*
+ * [0, 4, 3] => [0].children[4].children[3]
+ * */
+export function generateChildrenPathFromArray(indexes: number[]) {
+ if (!indexes.length) {
+ return '';
+ }
+
+ let resultPath = `[${indexes[0]}]`;
+
+ if (indexes.length > 1) {
+ for (let i = 1; i < indexes.length; i++) {
+ resultPath += `.children[${indexes[i]}]`;
+ }
+ }
+
+ return resultPath;
+}
+
+export function modifyObjectByPath(
+ blocks: ConstructorBlock[],
+ arrayPath: number[],
+ modifyCallback: (parentBlocks: ConstructorBlock[], index: number) => ConstructorBlock[],
+) {
+ // [1]
+ // [4].children[3]
+ const insertPath = generateChildrenPathFromArray(arrayPath);
+ // path: '' index: 1
+ // path: '[4].children' index: 3
+ const splitPath = splitPathAndIndex(insertPath);
+
+ if (splitPath) {
+ const {path: parentPath, index} = splitPath;
+ // Get Array that lies on path
+ const parentArray = parentPath ? _.get(blocks, parentPath) : blocks;
+
+ const value = Array.isArray(parentArray) ? parentArray : [];
+ // Modify Array
+ const newModifiedArray = modifyCallback(value, index);
+
+ // Return it back
+ return insertByPath(blocks, parentPath, newModifiedArray);
+ }
+
+ return blocks;
+}
+
+export function isItemsNeighbours(arrayA: number[], arrayB: number[]) {
+ if (arrayA.length !== arrayB.length) {
+ return false;
+ }
+
+ for (let i = 0; i < arrayA.length - 1; i++) {
+ if (arrayA[i] !== arrayB[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+export function getDestinationShiftBeforeReorder(arrayInit: number[], arrayDest: number[]) {
+ if (arrayInit.length === arrayDest.length || arrayInit.length > arrayDest.length) {
+ return arrayDest;
+ }
+
+ for (let i = 0; i < arrayInit.length; i++) {
+ if (arrayInit[i] < arrayDest[i]) {
+ return prepareShift(arrayInit, arrayDest);
+ }
+ }
+
+ return arrayDest;
+}
+
+export function prepareShift(arrayInit: number[], arrayDest: number[]) {
+ if (arrayInit.length === arrayDest.length || arrayInit.length > arrayDest.length) {
+ return arrayDest;
+ }
+
+ return arrayDest.map((pathIndex, index) =>
+ index === arrayInit.length - 1 ? pathIndex - 1 : pathIndex,
+ );
+}
+
+export const getUrlOrigin = (url: string) => {
+ try {
+ const urlObject = new URL(url);
+ return urlObject.origin;
+ } catch {
+ return undefined;
+ }
+};
+
+export const getItemTitle = (item: object): string | undefined => {
+ return (
+ _.get(item, 'title.text') ||
+ _.get(item, 'title') ||
+ _.get(item, 'textContent.title') ||
+ _.get(item, 'content.title')
+ );
+};
diff --git a/src/editor/components/CodeEditor/CodeEditor.tsx b/src/editor/components/CodeEditor/CodeEditor.tsx
index de0978a5c..d39f84822 100644
--- a/src/editor/components/CodeEditor/CodeEditor.tsx
+++ b/src/editor/components/CodeEditor/CodeEditor.tsx
@@ -1,5 +1,4 @@
import * as React from 'react';
-
import {ChevronsCollapseUpRight, ChevronsExpandUpRight} from '@gravity-ui/icons';
import {Button, Icon} from '@gravity-ui/uikit';
import debounce from 'lodash/debounce';
@@ -17,8 +16,6 @@ import './CodeEditor.scss';
const b = block('code-editor');
-const ON_CHANGE_DEBOUNCE_TIMEOUT = 300;
-
interface CodeEditorProps {
code: string;
fullscreenModeOn: boolean;
@@ -28,60 +25,57 @@ interface CodeEditorProps {
message?: CodeEditorMessageProps;
}
-export const CodeEditor = React.memo(
- ({onChange, validator, fullscreenModeOn, onFullscreenModeOnUpdate, code}: CodeEditorProps) => {
- const [message, setMessage] = React.useState(() => validator(code));
- const {theme = Theme.Light} = React.useContext(EditorContext);
+export const CodeEditor = ({
+ onChange,
+ validator,
+ fullscreenModeOn,
+ onFullscreenModeOnUpdate,
+ code,
+}: CodeEditorProps) => {
+ const [message, setMessage] = React.useState(() => validator(code));
+ const {theme = Theme.Light} = React.useContext(EditorContext);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- const onChangeWithValidation = React.useCallback(
- debounce((newCode: string) => {
- const validationResult = validator(newCode);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ const onChangeWithValidation = React.useCallback(
+ debounce((newCode: string) => {
+ const validationResult = validator(newCode);
- setMessage(validationResult);
- onChange(parseCode(newCode));
- }, ON_CHANGE_DEBOUNCE_TIMEOUT),
- [onChange, validator],
- );
+ setMessage(validationResult);
+ onChange(parseCode(newCode));
+ }, 200),
+ [onChange, validator],
+ );
- return (
-
-
- onFullscreenModeOnUpdate(!fullscreenModeOn)}
- >
-
-
-
-
-
+
+ onFullscreenModeOnUpdate(!fullscreenModeOn)}
+ >
+
-
-
+
- );
- },
-);
-
-CodeEditor.displayName = 'CodeEditor';
+
+
+
+
+
+ );
+};
diff --git a/src/editor/data/templates/test-editor-block.json b/src/editor/data/templates/test-editor-block.json
new file mode 100644
index 000000000..43c1534b1
--- /dev/null
+++ b/src/editor/data/templates/test-editor-block.json
@@ -0,0 +1,18 @@
+{
+ "template": {
+ "type": "test-editor-block",
+ "title": "Lorem ipsum dolor sit amet",
+ "table": {
+ "content": [
+ ["Lorem", "ipsum 1", "dolor 2", "sit 3"],
+ ["Lorem 1", "0", "0", "0"],
+ ["Lorem 2", "0", "0", "1"],
+ ["Lorem 3", "0", "0", "1"],
+ ["Lorem 4", "0", "1", "1"],
+ ["Lorem 5", "1", "1", "1"]
+ ],
+ "legend": ["ipsum 1", "ipsum 2"],
+ "justify": ["start", "center", "center", "center"]
+ }
+ }
+}
diff --git a/src/form-builder/FormBuilder.tsx b/src/form-builder/FormBuilder.tsx
new file mode 100644
index 000000000..1c7f3a9b0
--- /dev/null
+++ b/src/form-builder/FormBuilder.tsx
@@ -0,0 +1,14 @@
+'use client';
+
+import * as React from 'react';
+import {FormBuilderProps} from './types';
+import {FormBuilderBody} from './components/FormBuilderBody/FormBuilderBody';
+import {FormProvider} from './hooks/FormContext';
+
+export const FormBuilder: React.FC = ({formFields, className, onChange}) => {
+ return (
+
+
+
+ );
+};
diff --git a/src/form-builder/README.md b/src/form-builder/README.md
new file mode 100644
index 000000000..7ccd21f4a
--- /dev/null
+++ b/src/form-builder/README.md
@@ -0,0 +1,30 @@
+# FormBuilder
+
+Компонент для визуального создания и редактирования форм.
+
+## Как подключить
+
+```bash
+import {FormBuilder} from '@gravity-ui/page-constructor/form-builder';
+```
+
+## Пропсы
+
+| Пропс | Тип | Описание |
+| ------------ | ------------------------------- | ---------------------------------------------------- |
+| `formFields` | `FormField[]` | Массив полей формы, которые отображаются в редакторе |
+| `className` | `string` | Дополнительный CSS-класс для стилизации компонента |
+| `onChange` | `(fields: FormField[]) => void` | Колбэк, вызываемый при изменении структуры формы |
+
+## Примеры использования
+
+```tsx
+import React from 'react';
+import {FormBuilder, FormField} from '@gravity-ui/page-constructor/form-builder';
+
+const MyFormBuilder = () => {
+ const [formFields, setFormFields] = React.useState([]);
+
+ return ;
+};
+```
diff --git a/src/form-builder/components/AddPropertyButton/AddPropertyButton.scss b/src/form-builder/components/AddPropertyButton/AddPropertyButton.scss
new file mode 100644
index 000000000..20c62ba3e
--- /dev/null
+++ b/src/form-builder/components/AddPropertyButton/AddPropertyButton.scss
@@ -0,0 +1,7 @@
+@import '../../styles/variables.scss';
+
+$block: '.#{$ns-form-builder}add-property-button';
+
+#{$block} {
+ margin-top: 8px;
+}
diff --git a/src/form-builder/components/AddPropertyButton/AddPropertyButton.tsx b/src/form-builder/components/AddPropertyButton/AddPropertyButton.tsx
new file mode 100644
index 000000000..4dd49b631
--- /dev/null
+++ b/src/form-builder/components/AddPropertyButton/AddPropertyButton.tsx
@@ -0,0 +1,36 @@
+import * as React from 'react';
+import {Button, DropdownMenu} from '@gravity-ui/uikit';
+import {ConfigInput} from '../../../form-generator';
+import {formBuilderCn} from '../../utils/cn';
+
+import './AddPropertyButton.scss';
+
+const b = formBuilderCn('add-property-button');
+
+interface AddPropertyButtonProps {
+ inputTypeMenuItems: Array<{action: () => void; text: string; type: string}>;
+ onAdd: (type: ConfigInput['type']) => void;
+ buttonText?: string;
+}
+
+export const AddPropertyButton: React.FC = ({
+ inputTypeMenuItems,
+ onAdd,
+ buttonText = '+ Add Property',
+}) => (
+
+ ({
+ ...item,
+ action: () => {
+ onAdd(item.type as ConfigInput['type']);
+ },
+ }))}
+ renderSwitcher={(props) => (
+
+ {buttonText}
+
+ )}
+ />
+
+);
diff --git a/src/form-builder/components/ArrayFieldRenderer/ArrayFieldRenderer.scss b/src/form-builder/components/ArrayFieldRenderer/ArrayFieldRenderer.scss
new file mode 100644
index 000000000..fc789b431
--- /dev/null
+++ b/src/form-builder/components/ArrayFieldRenderer/ArrayFieldRenderer.scss
@@ -0,0 +1,33 @@
+@import '../../styles/variables.scss';
+
+$block: '.#{$ns-form-builder}array-field-renderer';
+
+#{$block} {
+ &__config {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ &__config-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ & > span {
+ min-width: 90px;
+ font-weight: 500;
+ }
+
+ & > div {
+ flex: 1;
+ }
+ }
+
+ &__nested-fields {
+ margin-top: 12px;
+ margin-left: 16px;
+ padding-left: 12px;
+ border-left: 2px solid var(--g-color-line-generic);
+ }
+}
diff --git a/src/form-builder/components/ArrayFieldRenderer/ArrayFieldRenderer.tsx b/src/form-builder/components/ArrayFieldRenderer/ArrayFieldRenderer.tsx
new file mode 100644
index 000000000..fcd5641e7
--- /dev/null
+++ b/src/form-builder/components/ArrayFieldRenderer/ArrayFieldRenderer.tsx
@@ -0,0 +1,80 @@
+import * as React from 'react';
+import {Button, DropdownMenu, Text} from '@gravity-ui/uikit';
+import {ArrayObjectInput, ConfigInput} from '../../../form-generator';
+import {ConfigRow} from '../ConfigRow/ConfigRow';
+import {AddPropertyButton} from '../AddPropertyButton/AddPropertyButton';
+import {SectionHeader} from '../SectionHeader/SectionHeader';
+import {useFormContext} from '../../hooks/FormContext';
+import {FormArrayField, InputTypeMenuItem} from '../../types';
+import {formBuilderCn} from '../../utils/cn';
+
+import './ArrayFieldRenderer.scss';
+
+const b = formBuilderCn('array-field-renderer');
+
+interface ArrayFieldRendererProps {
+ field: FormArrayField;
+ fieldName: string;
+ inputTypeMenuItems: InputTypeMenuItem[];
+ renderNestedField: (
+ field: ConfigInput,
+ index: number,
+ parentId: string,
+ optionIndex?: number,
+ ) => React.ReactNode;
+}
+
+export const ArrayFieldRenderer: React.FC = ({
+ field,
+ fieldName: _fieldName,
+ inputTypeMenuItems,
+ renderNestedField,
+}) => {
+ const {addObjectProperty, updateField} = useFormContext();
+
+ return (
+
+
+ Array Type:
+ updateField(field.id, {arrayType: 'text'}),
+ text: 'Text',
+ },
+ {
+ action: () => updateField(field.id, {arrayType: 'object'}),
+ text: 'Object',
+ },
+ ]}
+ renderSwitcher={(props) => (
+
+ {field.arrayType === 'object' ? 'Object' : 'Text'}
+
+ )}
+ />
+
+
+
updateField(field.id, {buttonText: value})}
+ />
+
+ {field.arrayType === 'object' && (
+
+
+
+ {(field as ArrayObjectInput)?.properties?.map((property, index) =>
+ renderNestedField(property, index, field.id),
+ )}
+
+
addObjectProperty(field.id, type)}
+ />
+
+ )}
+
+ );
+};
diff --git a/src/form-builder/components/ConfigRow/ConfigRow.scss b/src/form-builder/components/ConfigRow/ConfigRow.scss
new file mode 100644
index 000000000..ae1fa635c
--- /dev/null
+++ b/src/form-builder/components/ConfigRow/ConfigRow.scss
@@ -0,0 +1,18 @@
+@import '../../styles/variables.scss';
+
+$block: '.#{$ns-form-builder}config-row';
+
+#{$block} {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ & > span {
+ min-width: 90px;
+ font-weight: 500;
+ }
+
+ & > div {
+ flex: 1;
+ }
+}
diff --git a/src/form-builder/components/ConfigRow/ConfigRow.tsx b/src/form-builder/components/ConfigRow/ConfigRow.tsx
new file mode 100644
index 000000000..81b584470
--- /dev/null
+++ b/src/form-builder/components/ConfigRow/ConfigRow.tsx
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import {Text, TextInput} from '@gravity-ui/uikit';
+import {formBuilderCn} from '../../utils/cn';
+
+import './ConfigRow.scss';
+
+const b = formBuilderCn('config-row');
+
+interface ConfigRowProps {
+ label: string;
+ value: string;
+ onUpdate: (value: string) => void;
+}
+
+const ConfigRowComponent: React.FC = ({label, value, onUpdate}) => {
+ const handleUpdate = React.useCallback(
+ (newValue: string) => {
+ onUpdate(newValue);
+ },
+ [onUpdate],
+ );
+
+ return (
+
+ {label}:
+
+
+ );
+};
+
+export const ConfigRow = React.memo(ConfigRowComponent);
diff --git a/src/form-builder/components/FieldCard/FieldCard.scss b/src/form-builder/components/FieldCard/FieldCard.scss
new file mode 100644
index 000000000..be609fe05
--- /dev/null
+++ b/src/form-builder/components/FieldCard/FieldCard.scss
@@ -0,0 +1,21 @@
+@import '../../styles/variables.scss';
+
+$block: '.#{$ns-form-builder}field-card';
+
+#{$block} {
+ padding: 16px;
+
+ &__config {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ &__nested-field {
+ margin-top: 8px;
+ padding: 12px;
+ background-color: var(--g-color-base-generic);
+ border-radius: 6px;
+ border: 1px solid var(--g-color-line-generic);
+ }
+}
diff --git a/src/form-builder/components/FieldCard/FieldCard.tsx b/src/form-builder/components/FieldCard/FieldCard.tsx
new file mode 100644
index 000000000..8c7773033
--- /dev/null
+++ b/src/form-builder/components/FieldCard/FieldCard.tsx
@@ -0,0 +1,161 @@
+import * as React from 'react';
+import {Card} from '@gravity-ui/uikit';
+import {formBuilderCn} from '../../utils/cn';
+import {ConfigInput} from '../../../form-generator';
+import {ConfigRow} from '../ConfigRow/ConfigRow';
+import {FieldHeader} from '../FieldHeader/FieldHeader';
+import {ArrayFieldRenderer} from '../ArrayFieldRenderer/ArrayFieldRenderer';
+import {ObjectFieldRenderer} from '../ObjectFieldRenderer/ObjectFieldRenderer';
+import {OptionsRenderer} from '../OptionsRenderer/OptionsRenderer';
+import {SelectFieldRenderer} from '../SelectFieldRenderer/SelectFieldRenderer';
+import {useFormContext} from '../../hooks/FormContext';
+import {
+ FormAnyOfField,
+ FormArrayField,
+ FormField,
+ FormObjectField,
+ FormOneOfField,
+ InputTypeMenuItem,
+} from '../../types';
+
+import './FieldCard.scss';
+
+const b = formBuilderCn('field-card');
+
+interface FieldCardProps {
+ field: FormField;
+ inputTypeMenuItems: InputTypeMenuItem[];
+}
+
+export const FieldCard: React.FC = ({field, inputTypeMenuItems}) => {
+ const {
+ removeField,
+ updateField,
+ removeObjectProperty,
+ removeOptionProperty,
+ updateObjectProperty,
+ updateOptionProperty,
+ } = useFormContext();
+
+ const renderNestedField = (
+ nestedField: ConfigInput,
+ index: number,
+ parentId: string,
+ optionIndex?: number,
+ ) => {
+ const isInOption = optionIndex !== undefined;
+
+ const handleRemove = () => {
+ if (isInOption) {
+ removeOptionProperty(parentId, optionIndex, index);
+ } else {
+ removeObjectProperty(parentId, index);
+ }
+ };
+
+ const handleUpdate = (updates: Partial) => {
+ if (isInOption) {
+ updateOptionProperty(parentId, optionIndex, index, updates);
+ } else {
+ updateObjectProperty(parentId, index, updates);
+ }
+ };
+
+ return (
+
+
+
+
+ handleUpdate({name: value})}
+ />
+ handleUpdate({title: value})}
+ />
+
+ {/* eslint-disable-next-line @typescript-eslint/no-use-before-define */}
+ {renderFieldTypeSpecificContent(nestedField, parentId)}
+
+
+ );
+ };
+
+ const renderFieldTypeSpecificContent = (fieldConfig: ConfigInput, parentId: string) => {
+ switch (fieldConfig.type) {
+ case 'object':
+ return (
+
+ );
+
+ case 'oneOf':
+ return (
+
+ );
+
+ case 'anyOf':
+ return (
+
+ );
+
+ case 'select':
+ return ;
+
+ case 'array':
+ return (
+
+ );
+
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+ removeField(field.id)} />
+
+
+ updateField(field.id, {name: value})}
+ />
+ updateField(field.id, {title: value})}
+ />
+ {renderFieldTypeSpecificContent(field, field.id)}
+
+
+ );
+};
diff --git a/src/form-builder/components/FieldHeader/FieldHeader.scss b/src/form-builder/components/FieldHeader/FieldHeader.scss
new file mode 100644
index 000000000..188557399
--- /dev/null
+++ b/src/form-builder/components/FieldHeader/FieldHeader.scss
@@ -0,0 +1,10 @@
+@import '../../styles/variables.scss';
+
+$block: '.#{$ns-form-builder}field-header';
+
+#{$block} {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+}
diff --git a/src/form-builder/components/FieldHeader/FieldHeader.tsx b/src/form-builder/components/FieldHeader/FieldHeader.tsx
new file mode 100644
index 000000000..6fa387400
--- /dev/null
+++ b/src/form-builder/components/FieldHeader/FieldHeader.tsx
@@ -0,0 +1,28 @@
+import * as React from 'react';
+import {Button, Text} from '@gravity-ui/uikit';
+import {formBuilderCn} from '../../utils/cn';
+
+import './FieldHeader.scss';
+
+const b = formBuilderCn('field-header');
+
+interface FieldHeaderProps {
+ title: string;
+ onRemove: () => void;
+ variant?: 'subheader-2' | 'subheader-3';
+ buttonSize?: 's' | 'xs';
+}
+
+export const FieldHeader: React.FC = ({
+ title,
+ onRemove,
+ variant = 'subheader-2',
+ buttonSize = 's',
+}) => (
+
+ {title}
+
+ Remove
+
+
+);
diff --git a/src/form-builder/components/FormBuilderBody/FormBuilderBody.scss b/src/form-builder/components/FormBuilderBody/FormBuilderBody.scss
new file mode 100644
index 000000000..20f635a6b
--- /dev/null
+++ b/src/form-builder/components/FormBuilderBody/FormBuilderBody.scss
@@ -0,0 +1,114 @@
+@import '../../styles/variables.scss';
+
+$block: '.#{$ns-form-builder}form-builder-body';
+
+#{$block} {
+ width: 100%;
+
+ &__field {
+ margin-top: 16px;
+ }
+
+ &__fields-list {
+ margin-top: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+
+ &__field-card {
+ padding: 16px;
+ border: 1px solid #d1d5db;
+ border-radius: 8px;
+ background-color: #ffffff;
+ }
+
+ &__field-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+ }
+
+ &__field-config {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ &__config-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ & > span {
+ min-width: 90px;
+ font-weight: 500;
+ }
+
+ & > div {
+ flex: 1;
+ }
+ }
+
+ &__nested-fields {
+ margin-top: 12px;
+ margin-left: 16px;
+ padding-left: 12px;
+ border-left: 2px solid #e5e7eb;
+ }
+
+ &__nested-field {
+ margin-top: 8px;
+ padding: 12px;
+ background-color: #f9fafb;
+ border-radius: 6px;
+ border: 1px solid #e5e7eb;
+ }
+
+ &__nested-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+ }
+
+ &__option {
+ margin-top: 12px;
+ padding: 12px;
+ background-color: #f9fafb;
+ border-radius: 6px;
+ border: 1px solid #e5e7eb;
+ }
+
+ &__option-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+ }
+
+ &__add-field-button {
+ margin-top: 8px;
+ }
+
+ &__enum-option {
+ margin-top: 8px;
+ padding: 12px;
+ background-color: #f3f4f6;
+ border-radius: 6px;
+ border: 1px solid #e5e7eb;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+
+ .forms__config-row {
+ margin-bottom: 4px;
+ }
+
+ button {
+ align-self: flex-end;
+ margin-top: 4px;
+ }
+ }
+}
diff --git a/src/form-builder/components/FormBuilderBody/FormBuilderBody.tsx b/src/form-builder/components/FormBuilderBody/FormBuilderBody.tsx
new file mode 100644
index 000000000..e5bd67224
--- /dev/null
+++ b/src/form-builder/components/FormBuilderBody/FormBuilderBody.tsx
@@ -0,0 +1,91 @@
+import * as React from 'react';
+import {Button, DropdownMenu} from '@gravity-ui/uikit';
+import {formBuilderCn} from '../../utils/cn';
+import {FieldCard} from '../FieldCard/FieldCard';
+import {useFormContext} from '../../hooks/FormContext';
+import {InputTypeMenuItem} from '../../types';
+
+import './FormBuilderBody.scss';
+
+const b = formBuilderCn('form-builder-body');
+
+interface FormBuilderBodyProps {
+ className?: string;
+}
+
+export const FormBuilderBody: React.FC = ({className}) => {
+ const {formFields, addField} = useFormContext();
+
+ const inputTypeMenuItems: InputTypeMenuItem[] = [
+ {
+ type: 'text',
+ action: () => addField('text'),
+ text: 'Text Input',
+ },
+ {
+ type: 'number',
+ action: () => addField('number'),
+ text: 'Number Input',
+ },
+ {
+ type: 'boolean',
+ action: () => addField('boolean'),
+ text: 'Boolean Input',
+ },
+ {
+ type: 'textarea',
+ action: () => addField('textarea'),
+ text: 'Textarea Input',
+ },
+ {
+ type: 'select',
+ action: () => addField('select'),
+ text: 'Select Input',
+ },
+ {
+ type: 'object',
+ action: () => addField('object'),
+ text: 'Object Input',
+ },
+ {
+ type: 'array',
+ action: () => addField('array'),
+ text: 'Array Input',
+ },
+ {
+ type: 'oneOf',
+ action: () => addField('oneOf'),
+ text: 'OneOf Input',
+ },
+ {
+ type: 'anyOf',
+ action: () => addField('anyOf'),
+ text: 'AnyOf Input',
+ },
+ ];
+
+ return (
+
+
+ {formFields.map((field) => (
+
+ ))}
+
+
+
+ (
+
+ + Add Field
+
+ )}
+ />
+
+
+ );
+};
diff --git a/src/form-builder/components/ObjectFieldRenderer/ObjectFieldRenderer.scss b/src/form-builder/components/ObjectFieldRenderer/ObjectFieldRenderer.scss
new file mode 100644
index 000000000..a602dada4
--- /dev/null
+++ b/src/form-builder/components/ObjectFieldRenderer/ObjectFieldRenderer.scss
@@ -0,0 +1,12 @@
+@import '../../styles/variables.scss';
+
+$block: '.#{$ns-form-builder}object-field-renderer';
+
+#{$block} {
+ &__nested-fields {
+ margin-top: 12px;
+ margin-left: 16px;
+ padding-left: 12px;
+ border-left: 2px solid var(--g-color-line-generic);
+ }
+}
diff --git a/src/form-builder/components/ObjectFieldRenderer/ObjectFieldRenderer.tsx b/src/form-builder/components/ObjectFieldRenderer/ObjectFieldRenderer.tsx
new file mode 100644
index 000000000..30b65d72a
--- /dev/null
+++ b/src/form-builder/components/ObjectFieldRenderer/ObjectFieldRenderer.tsx
@@ -0,0 +1,47 @@
+import * as React from 'react';
+import {formBuilderCn} from '../../utils/cn';
+import {ConfigInput} from '../../../form-generator';
+import {AddPropertyButton} from '../AddPropertyButton/AddPropertyButton';
+import {SectionHeader} from '../SectionHeader/SectionHeader';
+import {useFormContext} from '../../hooks/FormContext';
+import {FormObjectField, InputTypeMenuItem} from '../../types';
+
+import './ObjectFieldRenderer.scss';
+
+const b = formBuilderCn('object-field-renderer');
+
+interface ObjectFieldRendererProps {
+ field: FormObjectField;
+ fieldName: string;
+ inputTypeMenuItems: InputTypeMenuItem[];
+ renderNestedField: (
+ field: ConfigInput,
+ index: number,
+ parentId: string,
+ optionIndex?: number,
+ ) => React.ReactNode;
+}
+
+export const ObjectFieldRenderer: React.FC = ({
+ field,
+ fieldName: _fieldName,
+ inputTypeMenuItems,
+ renderNestedField,
+}) => {
+ const {addObjectProperty} = useFormContext();
+
+ return (
+
+
+
+ {field.properties.map((property, index) =>
+ renderNestedField(property, index, field.id),
+ )}
+
+
addObjectProperty(field.id, type)}
+ />
+
+ );
+};
diff --git a/src/form-builder/components/OptionHeader/OptionHeader.scss b/src/form-builder/components/OptionHeader/OptionHeader.scss
new file mode 100644
index 000000000..4b625bcc4
--- /dev/null
+++ b/src/form-builder/components/OptionHeader/OptionHeader.scss
@@ -0,0 +1,10 @@
+@import '../../styles/variables.scss';
+
+$block: '.#{$ns-form-builder}option-header';
+
+#{$block} {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+}
diff --git a/src/form-builder/components/OptionHeader/OptionHeader.tsx b/src/form-builder/components/OptionHeader/OptionHeader.tsx
new file mode 100644
index 000000000..3f2962641
--- /dev/null
+++ b/src/form-builder/components/OptionHeader/OptionHeader.tsx
@@ -0,0 +1,17 @@
+import * as React from 'react';
+import {Text} from '@gravity-ui/uikit';
+import {formBuilderCn} from '../../utils/cn';
+
+import './OptionHeader.scss';
+
+const b = formBuilderCn('option-header');
+
+interface OptionHeaderProps {
+ title: string;
+}
+
+export const OptionHeader: React.FC = ({title}) => (
+
+ {title}
+
+);
diff --git a/src/form-builder/components/OptionsRenderer/OptionsRenderer.scss b/src/form-builder/components/OptionsRenderer/OptionsRenderer.scss
new file mode 100644
index 000000000..55e60bba3
--- /dev/null
+++ b/src/form-builder/components/OptionsRenderer/OptionsRenderer.scss
@@ -0,0 +1,42 @@
+@import '../../styles/variables.scss';
+
+$block: '.#{$ns-form-builder}options-renderer';
+
+#{$block} {
+ &__header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+ }
+
+ &__option-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+ }
+
+ &__add-option-button {
+ margin-left: 8px;
+ }
+
+ &__remove-option-button {
+ margin-left: 8px;
+ }
+
+ &__nested-fields {
+ margin-top: 12px;
+ margin-left: 16px;
+ padding-left: 12px;
+ border-left: 2px solid var(--g-color-line-generic);
+ }
+
+ &__option {
+ margin-top: 12px;
+ padding: 12px;
+ background-color: var(--g-color-base-background);
+ border-radius: 6px;
+ border: 1px solid var(--g-color-line-generic);
+ }
+}
diff --git a/src/form-builder/components/OptionsRenderer/OptionsRenderer.tsx b/src/form-builder/components/OptionsRenderer/OptionsRenderer.tsx
new file mode 100644
index 000000000..066e71ad8
--- /dev/null
+++ b/src/form-builder/components/OptionsRenderer/OptionsRenderer.tsx
@@ -0,0 +1,87 @@
+import * as React from 'react';
+import {formBuilderCn} from '../../utils/cn';
+import {Button} from '@gravity-ui/uikit';
+import {ConfigInput} from '../../../form-generator';
+import {AddPropertyButton} from '../AddPropertyButton/AddPropertyButton';
+import {OptionHeader} from '../OptionHeader/OptionHeader';
+import {SectionHeader} from '../SectionHeader/SectionHeader';
+import {useFormContext} from '../../hooks/FormContext';
+import {FormOptionsField, InputTypeMenuItem} from '../../types';
+
+import './OptionsRenderer.scss';
+
+const b = formBuilderCn('options-renderer');
+
+interface OptionsRendererProps {
+ field: FormOptionsField;
+ fieldName: string;
+ inputTypeMenuItems: InputTypeMenuItem[];
+ renderNestedField: (
+ field: ConfigInput,
+ index: number,
+ parentId: string,
+ optionIndex?: number,
+ ) => React.ReactNode;
+}
+
+export const OptionsRenderer: React.FC = ({
+ field,
+ fieldName: _fieldName,
+ inputTypeMenuItems,
+ renderNestedField,
+}) => {
+ const {addOptionProperty, addOption, removeOption} = useFormContext();
+
+ return (
+
+
+
+ {
+ addOption(field.id);
+ }}
+ className={b('add-option-button')}
+ >
+ + Add Option
+
+
+
+ {field.options.map((option, optionIndex) => (
+
+
+
+ {field.options.length > 1 && (
+ {
+ removeOption(field.id, optionIndex);
+ }}
+ className={b('remove-option-button')}
+ >
+ Remove
+
+ )}
+
+
+
+
+
+ {option.properties.map((property, propIndex) =>
+ renderNestedField(property, propIndex, field.id, optionIndex),
+ )}
+
+
{
+ addOptionProperty(field.id, optionIndex, type);
+ }}
+ />
+
+
+ ))}
+
+ );
+};
diff --git a/src/form-builder/components/SectionHeader/SectionHeader.scss b/src/form-builder/components/SectionHeader/SectionHeader.scss
new file mode 100644
index 000000000..9550ae434
--- /dev/null
+++ b/src/form-builder/components/SectionHeader/SectionHeader.scss
@@ -0,0 +1,7 @@
+@import '../../styles/variables.scss';
+
+$block: '.#{$ns-form-builder}section-header';
+
+#{$block} {
+ margin-bottom: 8px;
+}
diff --git a/src/form-builder/components/SectionHeader/SectionHeader.tsx b/src/form-builder/components/SectionHeader/SectionHeader.tsx
new file mode 100644
index 000000000..c9fef5d71
--- /dev/null
+++ b/src/form-builder/components/SectionHeader/SectionHeader.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import {Text} from '@gravity-ui/uikit';
+import {formBuilderCn} from '../../utils/cn';
+
+import './SectionHeader.scss';
+
+const b = formBuilderCn('section-header');
+
+interface SectionHeaderProps {
+ title: string;
+ variant?: 'body-1' | 'body-2';
+}
+
+export const SectionHeader: React.FC = ({title, variant = 'body-1'}) => (
+
+ {title}
+
+);
diff --git a/src/form-builder/components/SelectFieldRenderer/SelectFieldRenderer.scss b/src/form-builder/components/SelectFieldRenderer/SelectFieldRenderer.scss
new file mode 100644
index 000000000..851f4a5e1
--- /dev/null
+++ b/src/form-builder/components/SelectFieldRenderer/SelectFieldRenderer.scss
@@ -0,0 +1,49 @@
+@import '../../styles/variables.scss';
+
+$block: '.#{$ns-form-builder}select-field-renderer';
+
+#{$block} {
+ &__config-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ & > span {
+ min-width: 90px;
+ font-weight: 500;
+ }
+
+ & > div {
+ flex: 1;
+ }
+
+ &_vertical {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+ }
+
+ &__options-title {
+ margin-bottom: 8px;
+ }
+
+ &__enum-option {
+ margin-top: 8px;
+ padding: 12px;
+ background-color: var(--g-color-base-generic-ultralight);
+ border-radius: 6px;
+ border: 1px solid var(--g-color-line-generic);
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ &__remove-button {
+ align-self: flex-end;
+ margin-top: 4px;
+ }
+
+ &__add-button {
+ margin-top: 8px;
+ }
+}
diff --git a/src/form-builder/components/SelectFieldRenderer/SelectFieldRenderer.tsx b/src/form-builder/components/SelectFieldRenderer/SelectFieldRenderer.tsx
new file mode 100644
index 000000000..f5a8a338b
--- /dev/null
+++ b/src/form-builder/components/SelectFieldRenderer/SelectFieldRenderer.tsx
@@ -0,0 +1,143 @@
+import * as React from 'react';
+import {Button, DropdownMenu, Text, TextInput} from '@gravity-ui/uikit';
+import {formBuilderCn} from '../../utils/cn';
+import {FormField} from '../../types';
+import {useFormContext} from '../../hooks/FormContext';
+
+import './SelectFieldRenderer.scss';
+
+const b = formBuilderCn('select-field-renderer');
+
+interface SelectFieldRendererProps {
+ field: FormField;
+}
+
+interface SelectFieldOption {
+ content: string;
+ value: string;
+}
+
+export const SelectFieldRenderer: React.FC = ({field}) => {
+ const {updateField} = useFormContext();
+
+ if (field.type !== 'select') {
+ return null;
+ }
+
+ const view = field.view as 'select' | 'radiobutton';
+ const mode = field.mode as 'single' | 'multiple';
+ const enumValues = (field.enum || []) as SelectFieldOption[];
+
+ return (
+
+
+ View:
+ updateField(field.id, {view: 'select'}),
+ text: 'Select',
+ },
+ {
+ action: () => updateField(field.id, {view: 'radiobutton'}),
+ text: 'Radio Button',
+ },
+ ]}
+ renderSwitcher={(props) => (
+
+ {view === 'radiobutton' ? 'Radio Button' : 'Select'}
+
+ )}
+ />
+
+
+
+ Mode:
+ updateField(field.id, {mode: 'single'}),
+ text: 'Single',
+ },
+ {
+ action: () => updateField(field.id, {mode: 'multiple'}),
+ text: 'Multiple',
+ },
+ ]}
+ renderSwitcher={(props) => (
+
+ {mode === 'multiple' ? 'Multiple' : 'Single'}
+
+ )}
+ />
+
+
+
+
+ Options:
+
+ {enumValues.map((option, index) => (
+
+
+ Label:
+ {
+ const newEnum = [...enumValues];
+ newEnum[index] = {
+ ...newEnum[index],
+ content: value,
+ };
+ updateField(field.id, {enum: newEnum});
+ }}
+ size="s"
+ />
+
+
+ Value:
+ {
+ const newEnum = [...enumValues];
+ newEnum[index] = {
+ ...newEnum[index],
+ value,
+ };
+ updateField(field.id, {enum: newEnum});
+ }}
+ size="s"
+ />
+
+
{
+ const newEnum = [...enumValues];
+ newEnum.splice(index, 1);
+ updateField(field.id, {enum: newEnum});
+ }}
+ className={b('remove-button')}
+ >
+ Remove
+
+
+ ))}
+
{
+ const newEnum = [...(enumValues || [])];
+ newEnum.push({
+ content: `Option ${newEnum.length + 1}`,
+ value: `option${newEnum.length + 1}`,
+ });
+ updateField(field.id, {enum: newEnum});
+ }}
+ className={b('add-button')}
+ >
+ + Add Option
+
+
+
+ );
+};
diff --git a/src/form-builder/hooks/FormContext.tsx b/src/form-builder/hooks/FormContext.tsx
new file mode 100644
index 000000000..bbb65b9d6
--- /dev/null
+++ b/src/form-builder/hooks/FormContext.tsx
@@ -0,0 +1,26 @@
+import * as React from 'react';
+import {useFormFields} from './useFormFields';
+import {FormContextType, FormField} from '../types';
+
+export const FormContext = React.createContext({} as FormContextType);
+
+interface FormProviderProps {
+ children: React.ReactNode;
+ formFields: FormField[];
+ onChange?: (fields: FormField[]) => void;
+}
+
+export const FormProvider: React.FC = ({children, formFields, onChange}) => {
+ const formFieldsData = useFormFields({initialFields: formFields, onChange});
+ return {children} ;
+};
+
+export const useFormContext = () => {
+ const context = React.useContext(FormContext);
+
+ if (!context) {
+ throw new Error('useFormContext must be used within a FormProvider');
+ }
+
+ return context;
+};
diff --git a/src/form-builder/hooks/useFormFields.ts b/src/form-builder/hooks/useFormFields.ts
new file mode 100644
index 000000000..c241907ce
--- /dev/null
+++ b/src/form-builder/hooks/useFormFields.ts
@@ -0,0 +1,863 @@
+import * as React from 'react';
+import {AnyOfInput, ConfigInput, ObjectInput, OneOfInput} from '../../form-generator';
+import {FormArrayField, FormField} from '../types';
+
+interface UseFormFieldsProps {
+ initialFields: FormField[];
+ onChange?: (fields: FormField[]) => void;
+}
+
+export const useFormFields = ({initialFields, onChange}: UseFormFieldsProps) => {
+ const [formFields, setFormFields] = React.useState(initialFields);
+
+ const [nextId, setNextId] = React.useState(initialFields.length + 1);
+
+ const generateId = React.useCallback(() => {
+ const id = `field_id_${nextId}`;
+ setNextId((prev) => prev + 1);
+ return id;
+ }, [nextId]);
+
+ const generateName = React.useCallback(() => {
+ const name = `field_${nextId}`;
+ return name;
+ }, [nextId]);
+
+ const updateFormFields = React.useCallback(
+ (fields: FormField[]) => {
+ setFormFields(fields);
+ onChange?.(fields);
+ },
+ [onChange],
+ );
+
+ const createField = React.useCallback(
+ (type: ConfigInput['type'], name = ''): FormField => {
+ const fieldName = name || generateName();
+ const fieldId = generateId();
+ let newField: FormField;
+
+ switch (type) {
+ case 'text':
+ newField = {
+ type: 'text',
+ name: fieldName,
+ title: 'Text Input',
+ id: fieldId,
+ } as FormField;
+ break;
+ case 'number':
+ newField = {
+ type: 'number',
+ name: fieldName,
+ title: 'Number Input',
+ id: fieldId,
+ } as FormField;
+ break;
+ case 'boolean':
+ newField = {
+ type: 'boolean',
+ name: fieldName,
+ title: 'Boolean Input',
+ id: fieldId,
+ } as FormField;
+ break;
+ case 'textarea':
+ newField = {
+ type: 'textarea',
+ name: fieldName,
+ title: 'Textarea Input',
+ id: fieldId,
+ } as FormField;
+ break;
+ case 'select':
+ newField = {
+ type: 'select',
+ name: fieldName,
+ title: 'Select Input',
+ view: 'select',
+ mode: 'single',
+ enum: [
+ {content: 'Option 1', value: 'option1'},
+ {content: 'Option 2', value: 'option2'},
+ ],
+ id: fieldId,
+ } as FormField;
+ break;
+ case 'object':
+ newField = {
+ type: 'object',
+ name: fieldName,
+ title: 'Object Input',
+ properties: [
+ {
+ type: 'text',
+ name: 'property1',
+ title: 'Property 1',
+ id: generateId(),
+ } as FormField,
+ ],
+ id: fieldId,
+ } as FormField;
+ break;
+ case 'array':
+ newField = {
+ type: 'array',
+ name: fieldName,
+ title: 'Array Input',
+ buttonText: 'Add Item',
+ arrayType: 'text',
+ id: fieldId,
+ } as FormField;
+ break;
+ case 'oneOf':
+ newField = {
+ type: 'oneOf',
+ name: fieldName,
+ title: 'OneOf Input',
+ options: [
+ {
+ title: 'Option A',
+ value: 'optionA',
+ properties: [
+ {
+ type: 'text',
+ name: 'textField',
+ title: 'Text Field',
+ id: generateId(),
+ } as FormField,
+ ],
+ },
+ {
+ title: 'Option B',
+ value: 'optionB',
+ properties: [
+ {
+ type: 'number',
+ name: 'numberField',
+ title: 'Number Field',
+ id: generateId(),
+ } as FormField,
+ ],
+ },
+ ],
+ id: fieldId,
+ } as FormField;
+ break;
+ case 'anyOf':
+ newField = {
+ type: 'anyOf',
+ name: fieldName,
+ title: 'AnyOf Input',
+ options: [
+ {
+ title: 'Option A',
+ value: 'optionA',
+ properties: [
+ {
+ type: 'text',
+ name: 'textField',
+ title: 'Text Field',
+ id: generateId(),
+ } as FormField,
+ ],
+ },
+ {
+ title: 'Option B',
+ value: 'optionB',
+ properties: [
+ {
+ type: 'boolean',
+ name: 'booleanField',
+ title: 'Boolean Field',
+ id: generateId(),
+ } as FormField,
+ ],
+ },
+ ],
+ id: fieldId,
+ } as FormField;
+ break;
+ default:
+ return createField('text', fieldName);
+ }
+
+ return newField;
+ },
+ [generateId, generateName],
+ );
+
+ const updateFieldById = React.useCallback(
+ (fields: FormField[], fieldId: string, updates: Partial): FormField[] => {
+ return fields.map((field) => {
+ if (field.id === fieldId) {
+ return {...field, ...updates} as FormField;
+ }
+
+ if (field.type === 'object' && (field as ObjectInput).properties) {
+ const objectField = field as ObjectInput;
+ return {
+ ...field,
+ properties: updateFieldById(
+ objectField.properties as FormField[],
+ fieldId,
+ updates,
+ ),
+ } as FormField;
+ }
+
+ if (
+ (field.type === 'oneOf' || field.type === 'anyOf') &&
+ (field as OneOfInput | AnyOfInput).options
+ ) {
+ const optionsField = field as OneOfInput | AnyOfInput;
+ return {
+ ...field,
+ options: optionsField.options.map((option) => ({
+ ...option,
+ properties: updateFieldById(
+ option.properties as FormField[],
+ fieldId,
+ updates,
+ ),
+ })),
+ } as FormField;
+ }
+
+ if (field.type === 'array' && field.arrayType === 'object') {
+ const arrayField = field as FormArrayField;
+ return {
+ ...field,
+ properties: updateFieldById(
+ arrayField.properties as FormField[],
+ fieldId,
+ updates,
+ ),
+ } as FormField;
+ }
+
+ return field;
+ });
+ },
+ [],
+ );
+
+ const addField = React.useCallback(
+ (type: ConfigInput['type']) => {
+ const newField = createField(type);
+ updateFormFields([...formFields, newField]);
+ },
+ [createField, formFields, updateFormFields],
+ );
+
+ const removeField = React.useCallback(
+ (fieldId: string) => {
+ updateFormFields(formFields.filter((field) => field.id !== fieldId));
+ },
+ [formFields, updateFormFields],
+ );
+
+ const updateField = React.useCallback(
+ (fieldId: string, updates: Partial) => {
+ updateFormFields(updateFieldById(formFields, fieldId, updates));
+ },
+ [formFields, updateFieldById, updateFormFields],
+ );
+
+ const addPropertyToObjectById = React.useCallback(
+ (
+ fields: FormField[],
+ objectId: string,
+ type: ConfigInput['type'] = 'text',
+ ): FormField[] => {
+ return fields.map((field) => {
+ if (field.id === objectId && field.type === 'object') {
+ const objectField = field as ObjectInput;
+ const propertyName = `property${(objectField.properties?.length || 0) + 1}`;
+ const newProperty = createField(type, propertyName);
+
+ return {
+ ...field,
+ properties: [...(objectField.properties || []), newProperty],
+ } as FormField;
+ }
+
+ if (field.type === 'object' && (field as ObjectInput).properties) {
+ const objectField = field as ObjectInput;
+ return {
+ ...field,
+ properties: addPropertyToObjectById(
+ objectField.properties as FormField[],
+ objectId,
+ type,
+ ),
+ } as FormField;
+ }
+
+ if (
+ (field.type === 'oneOf' || field.type === 'anyOf') &&
+ (field as OneOfInput | AnyOfInput).options
+ ) {
+ const optionsField = field as OneOfInput | AnyOfInput;
+ return {
+ ...field,
+ options: optionsField.options.map((option) => ({
+ ...option,
+ properties: addPropertyToObjectById(
+ option.properties as FormField[],
+ objectId,
+ type,
+ ),
+ })),
+ } as FormField;
+ }
+
+ if (field.type === 'array' && field.arrayType === 'object') {
+ const arrayField = field as FormArrayField;
+
+ if (field.id === objectId) {
+ const propertyName = `property${(arrayField.properties?.length || 0) + 1}`;
+ const newProperty = createField(type, propertyName);
+
+ return {
+ ...field,
+ properties: [...(arrayField.properties || []), newProperty],
+ } as FormField;
+ }
+
+ if (arrayField.properties && arrayField.properties.length > 0) {
+ return {
+ ...field,
+ properties: addPropertyToObjectById(
+ arrayField.properties,
+ objectId,
+ type,
+ ),
+ } as FormField;
+ }
+ }
+
+ return field;
+ });
+ },
+ [createField],
+ );
+
+ const addObjectProperty = React.useCallback(
+ (objectId: string, type: ConfigInput['type'] = 'text') => {
+ updateFormFields(addPropertyToObjectById(formFields, objectId, type));
+ },
+ [formFields, addPropertyToObjectById, updateFormFields],
+ );
+
+ const addOptionProperty = React.useCallback(
+ (fieldId: string, optionIndex: number, type: ConfigInput['type'] = 'text') => {
+ const addPropertyToOptionRecursive = (
+ fields: FormField[],
+ targetFieldId: string,
+ targetOptionIndex: number,
+ ): FormField[] => {
+ return fields.map((field) => {
+ if (
+ field.id === targetFieldId &&
+ (field.type === 'oneOf' || field.type === 'anyOf')
+ ) {
+ const updatedField = JSON.parse(JSON.stringify(field));
+ const options = updatedField.options;
+
+ if (
+ options &&
+ Array.isArray(options) &&
+ targetOptionIndex >= 0 &&
+ targetOptionIndex < options.length &&
+ options[targetOptionIndex].properties
+ ) {
+ const propertyName = `property${options[targetOptionIndex].properties.length + 1}`;
+ const newProperty = createField(type, propertyName);
+
+ options[targetOptionIndex].properties.push(newProperty);
+ }
+
+ return updatedField;
+ }
+
+ if (field.type === 'object' && (field as ObjectInput).properties) {
+ const objectField = field as ObjectInput;
+ return {
+ ...field,
+ properties: addPropertyToOptionRecursive(
+ objectField.properties as FormField[],
+ targetFieldId,
+ targetOptionIndex,
+ ),
+ } as FormField;
+ }
+
+ if (
+ (field.type === 'oneOf' || field.type === 'anyOf') &&
+ (field as OneOfInput | AnyOfInput).options
+ ) {
+ const optionsField = field as OneOfInput | AnyOfInput;
+ return {
+ ...field,
+ options: optionsField.options.map((option) => ({
+ ...option,
+ properties: addPropertyToOptionRecursive(
+ option.properties as FormField[],
+ targetFieldId,
+ targetOptionIndex,
+ ),
+ })),
+ } as FormField;
+ }
+
+ if (
+ field.type === 'array' &&
+ field.arrayType === 'object' &&
+ (field as FormArrayField).properties
+ ) {
+ const arrayField = field as FormArrayField;
+ return {
+ ...field,
+ properties: addPropertyToOptionRecursive(
+ arrayField.properties as FormField[],
+ targetFieldId,
+ targetOptionIndex,
+ ),
+ } as FormField;
+ }
+
+ return field;
+ });
+ };
+
+ setFormFields((prev) => addPropertyToOptionRecursive(prev, fieldId, optionIndex));
+ },
+ [createField],
+ );
+
+ const removeObjectProperty = React.useCallback(
+ (fieldId: string, propertyIndex: number) => {
+ updateFormFields(
+ formFields.map((field) => {
+ if (field.id !== fieldId) return field;
+
+ const updatedField = JSON.parse(JSON.stringify(field));
+ const objectInput = updatedField as ObjectInput;
+
+ objectInput.properties.splice(propertyIndex, 1);
+
+ return updatedField;
+ }) as FormField[],
+ );
+ },
+ [formFields, updateFormFields],
+ );
+
+ const removeOptionProperty = React.useCallback(
+ (fieldId: string, optionIndex: number, propertyIndex: number) => {
+ const removePropertyFromOptionRecursive = (
+ fields: FormField[],
+ targetFieldId: string,
+ targetOptionIndex: number,
+ targetPropertyIndex: number,
+ ): FormField[] => {
+ return fields.map((field) => {
+ if (
+ field.id === targetFieldId &&
+ (field.type === 'oneOf' || field.type === 'anyOf')
+ ) {
+ const updatedField = JSON.parse(JSON.stringify(field));
+ const options = updatedField.options;
+
+ if (
+ options &&
+ Array.isArray(options) &&
+ targetOptionIndex >= 0 &&
+ targetOptionIndex < options.length &&
+ options[targetOptionIndex].properties &&
+ targetPropertyIndex >= 0 &&
+ targetPropertyIndex < options[targetOptionIndex].properties.length
+ ) {
+ options[targetOptionIndex].properties.splice(targetPropertyIndex, 1);
+ }
+
+ return updatedField;
+ }
+
+ if (field.type === 'object' && (field as ObjectInput).properties) {
+ const objectField = field as ObjectInput;
+ return {
+ ...field,
+ properties: removePropertyFromOptionRecursive(
+ objectField.properties as FormField[],
+ targetFieldId,
+ targetOptionIndex,
+ targetPropertyIndex,
+ ),
+ } as FormField;
+ }
+
+ if (
+ (field.type === 'oneOf' || field.type === 'anyOf') &&
+ (field as OneOfInput | AnyOfInput).options
+ ) {
+ const optionsField = field as OneOfInput | AnyOfInput;
+ return {
+ ...field,
+ options: optionsField.options.map((option) => ({
+ ...option,
+ properties: removePropertyFromOptionRecursive(
+ option.properties as FormField[],
+ targetFieldId,
+ targetOptionIndex,
+ targetPropertyIndex,
+ ),
+ })),
+ } as FormField;
+ }
+
+ if (
+ field.type === 'array' &&
+ field.arrayType === 'object' &&
+ (field as FormArrayField).properties
+ ) {
+ const arrayField = field as FormArrayField;
+ return {
+ ...field,
+ properties: removePropertyFromOptionRecursive(
+ arrayField.properties as FormField[],
+ targetFieldId,
+ targetOptionIndex,
+ targetPropertyIndex,
+ ),
+ } as FormField;
+ }
+
+ return field;
+ });
+ };
+
+ setFormFields((prev) =>
+ removePropertyFromOptionRecursive(prev, fieldId, optionIndex, propertyIndex),
+ );
+ },
+ [],
+ );
+
+ const updateObjectProperty = React.useCallback(
+ (fieldId: string, propertyIndex: number, updates: Partial) => {
+ updateFormFields(
+ formFields.map((field) => {
+ if (field.id !== fieldId) return field;
+
+ const updatedField = JSON.parse(JSON.stringify(field));
+ const objectInput = updatedField as ObjectInput;
+
+ objectInput.properties[propertyIndex] = {
+ ...objectInput.properties[propertyIndex],
+ ...updates,
+ } as ConfigInput;
+
+ return updatedField;
+ }) as FormField[],
+ );
+ },
+ [formFields, updateFormFields],
+ );
+
+ const updateOptionProperty = React.useCallback(
+ (
+ fieldId: string,
+ optionIndex: number,
+ propertyIndex: number,
+ updates: Partial,
+ ) => {
+ const updatePropertyInOptionRecursive = (
+ fields: FormField[],
+ targetFieldId: string,
+ targetOptionIndex: number,
+ targetPropertyIndex: number,
+ propertyUpdates: Partial,
+ ): FormField[] => {
+ return fields.map((field) => {
+ if (
+ field.id === targetFieldId &&
+ (field.type === 'oneOf' || field.type === 'anyOf')
+ ) {
+ const updatedField = JSON.parse(JSON.stringify(field));
+ const options = updatedField.options;
+
+ if (
+ options &&
+ Array.isArray(options) &&
+ targetOptionIndex >= 0 &&
+ targetOptionIndex < options.length &&
+ options[targetOptionIndex].properties &&
+ targetPropertyIndex >= 0 &&
+ targetPropertyIndex < options[targetOptionIndex].properties.length
+ ) {
+ options[targetOptionIndex].properties[targetPropertyIndex] = {
+ ...options[targetOptionIndex].properties[targetPropertyIndex],
+ ...propertyUpdates,
+ } as ConfigInput;
+ }
+
+ return updatedField;
+ }
+
+ if (field.type === 'object' && (field as ObjectInput).properties) {
+ const objectField = field as ObjectInput;
+ return {
+ ...field,
+ properties: updatePropertyInOptionRecursive(
+ objectField.properties as FormField[],
+ targetFieldId,
+ targetOptionIndex,
+ targetPropertyIndex,
+ propertyUpdates,
+ ),
+ } as FormField;
+ }
+
+ if (
+ (field.type === 'oneOf' || field.type === 'anyOf') &&
+ (field as OneOfInput | AnyOfInput).options
+ ) {
+ const optionsField = field as OneOfInput | AnyOfInput;
+ return {
+ ...field,
+ options: optionsField.options.map((option) => ({
+ ...option,
+ properties: updatePropertyInOptionRecursive(
+ option.properties as FormField[],
+ targetFieldId,
+ targetOptionIndex,
+ targetPropertyIndex,
+ propertyUpdates,
+ ),
+ })),
+ } as FormField;
+ }
+
+ if (
+ field.type === 'array' &&
+ field.arrayType === 'object' &&
+ (field as FormArrayField).properties
+ ) {
+ const arrayField = field as FormArrayField;
+ return {
+ ...field,
+ properties: updatePropertyInOptionRecursive(
+ arrayField.properties as FormField[],
+ targetFieldId,
+ targetOptionIndex,
+ targetPropertyIndex,
+ propertyUpdates,
+ ),
+ } as FormField;
+ }
+
+ return field;
+ });
+ };
+
+ setFormFields((prev) =>
+ updatePropertyInOptionRecursive(prev, fieldId, optionIndex, propertyIndex, updates),
+ );
+ },
+ [],
+ );
+
+ const addOption = React.useCallback(
+ (fieldId: string) => {
+ const addOptionRecursive = (
+ fields: FormField[],
+ targetFieldId: string,
+ ): FormField[] => {
+ return fields.map((field) => {
+ if (
+ field.id === targetFieldId &&
+ (field.type === 'oneOf' || field.type === 'anyOf')
+ ) {
+ const updatedField = JSON.parse(JSON.stringify(field));
+ const options = updatedField.options;
+
+ if (options && Array.isArray(options)) {
+ const optionLetter = String.fromCharCode(65 + options.length);
+ const newOption = {
+ title: `Option ${optionLetter}`,
+ value: `option${optionLetter.toLowerCase()}`,
+ properties: [
+ {
+ type: 'text',
+ name: 'textField',
+ title: 'Text Field',
+ id: generateId(),
+ } as FormField,
+ ],
+ };
+
+ options.push(newOption);
+ }
+
+ return updatedField;
+ }
+
+ if (field.type === 'object' && (field as ObjectInput).properties) {
+ const objectField = field as ObjectInput;
+ return {
+ ...field,
+ properties: addOptionRecursive(
+ objectField.properties as FormField[],
+ targetFieldId,
+ ),
+ } as FormField;
+ }
+
+ if (
+ (field.type === 'oneOf' || field.type === 'anyOf') &&
+ (field as OneOfInput | AnyOfInput).options
+ ) {
+ const optionsField = field as OneOfInput | AnyOfInput;
+ return {
+ ...field,
+ options: optionsField.options.map((option) => ({
+ ...option,
+ properties: addOptionRecursive(
+ option.properties as FormField[],
+ targetFieldId,
+ ),
+ })),
+ } as FormField;
+ }
+
+ if (
+ field.type === 'array' &&
+ field.arrayType === 'object' &&
+ (field as FormArrayField).properties
+ ) {
+ const arrayField = field as FormArrayField;
+ return {
+ ...field,
+ properties: addOptionRecursive(
+ arrayField.properties as FormField[],
+ targetFieldId,
+ ),
+ } as FormField;
+ }
+
+ return field;
+ });
+ };
+
+ setFormFields((prev) => addOptionRecursive(prev, fieldId));
+ },
+ [generateId],
+ );
+
+ const removeOption = React.useCallback(
+ (fieldId: string, optionIndex: number) => {
+ const removeOptionRecursive = (
+ fields: FormField[],
+ targetFieldId: string,
+ targetOptionIndex: number,
+ ): FormField[] => {
+ return fields.map((field) => {
+ if (
+ field.id === targetFieldId &&
+ (field.type === 'oneOf' || field.type === 'anyOf')
+ ) {
+ const updatedField = JSON.parse(JSON.stringify(field));
+ const options = updatedField.options;
+
+ if (
+ options &&
+ Array.isArray(options) &&
+ targetOptionIndex >= 0 &&
+ targetOptionIndex < options.length
+ ) {
+ if (options.length > 1) {
+ options.splice(targetOptionIndex, 1);
+ }
+ }
+
+ return updatedField;
+ }
+
+ if (field.type === 'object' && (field as ObjectInput).properties) {
+ const objectField = field as ObjectInput;
+ return {
+ ...field,
+ properties: removeOptionRecursive(
+ objectField.properties as FormField[],
+ targetFieldId,
+ targetOptionIndex,
+ ),
+ } as FormField;
+ }
+
+ if (
+ (field.type === 'oneOf' || field.type === 'anyOf') &&
+ (field as OneOfInput | AnyOfInput).options
+ ) {
+ const optionsField = field as OneOfInput | AnyOfInput;
+ return {
+ ...field,
+ options: optionsField.options.map((option) => ({
+ ...option,
+ properties: removeOptionRecursive(
+ option.properties as FormField[],
+ targetFieldId,
+ targetOptionIndex,
+ ),
+ })),
+ } as FormField;
+ }
+
+ if (
+ field.type === 'array' &&
+ field.arrayType === 'object' &&
+ (field as FormArrayField).properties
+ ) {
+ const arrayField = field as FormArrayField;
+ return {
+ ...field,
+ properties: removeOptionRecursive(
+ arrayField.properties as FormField[],
+ targetFieldId,
+ targetOptionIndex,
+ ),
+ } as FormField;
+ }
+
+ return field;
+ });
+ };
+
+ updateFormFields(removeOptionRecursive(formFields, fieldId, optionIndex));
+ },
+ [formFields, updateFormFields],
+ );
+
+ const resetForm = React.useCallback(() => {
+ updateFormFields([]);
+ }, [updateFormFields]);
+
+ return {
+ formFields,
+ createField,
+ addField,
+ removeField,
+ updateField,
+ addObjectProperty,
+ addOptionProperty,
+ removeObjectProperty,
+ removeOptionProperty,
+ updateObjectProperty,
+ updateOptionProperty,
+ addOption,
+ removeOption,
+ resetForm,
+ };
+};
diff --git a/src/form-builder/index.ts b/src/form-builder/index.ts
new file mode 100644
index 000000000..3cae346ba
--- /dev/null
+++ b/src/form-builder/index.ts
@@ -0,0 +1,4 @@
+export * from './FormBuilder';
+export * from './hooks/FormContext';
+export * from './hooks/useFormFields';
+export * from './types';
diff --git a/src/form-builder/styles/variables.scss b/src/form-builder/styles/variables.scss
new file mode 100644
index 000000000..2cff7415e
--- /dev/null
+++ b/src/form-builder/styles/variables.scss
@@ -0,0 +1 @@
+$ns-form-builder: 'pcformbuilder-';
diff --git a/src/form-builder/types.ts b/src/form-builder/types.ts
new file mode 100644
index 000000000..06830a31d
--- /dev/null
+++ b/src/form-builder/types.ts
@@ -0,0 +1,69 @@
+import {
+ AnyOfInput,
+ ArrayObjectInput,
+ ArrayTextInput,
+ ConfigInput,
+ ObjectInput,
+ OneOfInput,
+} from '../form-generator';
+
+export type FormField = ConfigInput & {id: string};
+
+export type FormObjectField = ObjectInput & {id: string};
+export type FormArrayField = (ArrayTextInput | ArrayObjectInput) & {
+ id: string;
+ properties?: FormField[];
+};
+export type FormOneOfField = OneOfInput & {id: string};
+export type FormAnyOfField = AnyOfInput & {id: string};
+export type FormOptionsField = (OneOfInput | AnyOfInput) & {id: string};
+
+export interface InputTypeMenuItem {
+ action: () => void;
+ text: string;
+ type: string;
+}
+
+export interface ContentConfig {
+ [key: string]: unknown;
+}
+
+export interface FormFieldsActions {
+ createField: (type: ConfigInput['type'], name?: string) => FormField;
+ addField: (type: ConfigInput['type']) => void;
+ removeField: (fieldId: string) => void;
+ updateField: (fieldId: string, updates: Partial) => void;
+
+ addObjectProperty: (objectId: string, type: ConfigInput['type']) => void;
+ removeObjectProperty: (fieldId: string, propertyIndex: number) => void;
+ updateObjectProperty: (
+ fieldId: string,
+ propertyIndex: number,
+ updates: Partial,
+ ) => void;
+
+ addOption: (fieldId: string) => void;
+ removeOption: (fieldId: string, optionIndex: number) => void;
+ addOptionProperty: (fieldId: string, optionIndex: number, type?: ConfigInput['type']) => void;
+ removeOptionProperty: (fieldId: string, optionIndex: number, propertyIndex: number) => void;
+ updateOptionProperty: (
+ fieldId: string,
+ optionIndex: number,
+ propertyIndex: number,
+ updates: Partial,
+ ) => void;
+
+ resetForm: () => void;
+}
+
+export interface FormData {
+ formFields: Array;
+}
+
+export interface FormContextType extends FormData, FormFieldsActions {}
+
+export interface FormBuilderProps {
+ className?: string;
+ formFields: Array;
+ onChange?: (fields: FormField[]) => void;
+}
diff --git a/src/form-builder/utils/cn.ts b/src/form-builder/utils/cn.ts
new file mode 100644
index 000000000..ece877dc9
--- /dev/null
+++ b/src/form-builder/utils/cn.ts
@@ -0,0 +1,5 @@
+import {withNaming} from '@bem-react/classname';
+
+export const FORM_BUILDER_NAMESPACE = 'pcformbuilder-';
+
+export const formBuilderCn = withNaming({n: FORM_BUILDER_NAMESPACE, e: '__', m: '_'});
diff --git a/src/form-generator/FormGenerator.scss b/src/form-generator/FormGenerator.scss
new file mode 100644
index 000000000..95d96eee2
--- /dev/null
+++ b/src/form-generator/FormGenerator.scss
@@ -0,0 +1,10 @@
+@import './styles/variables.scss';
+@import './styles/mixins.scss';
+
+$block: '.#{$ns-form-generator}form-generator';
+
+#{$block} {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
diff --git a/src/form-generator/FormGenerator.tsx b/src/form-generator/FormGenerator.tsx
new file mode 100644
index 000000000..ad3e239a4
--- /dev/null
+++ b/src/form-generator/FormGenerator.tsx
@@ -0,0 +1,249 @@
+import _ from 'lodash';
+import * as React from 'react';
+
+import {ConfigInput, DynamicFormValue} from './types';
+import {formGeneratorCn} from './utils/cn';
+
+import ArrayDynamicField from './components/Fields/Array/Array';
+import BooleanDynamicField from './components/Fields/Boolean/Boolean';
+import NumberDynamicField from './components/Fields/Number/Number';
+import ObjectDynamicField from './components/Fields/Object/Object';
+import AnyOfDynamicField from './components/Fields/AnyOf/AnyOf';
+import OneOfDynamicField from './components/Fields/OneOf/OneOf';
+import SelectDynamicField from './components/Fields/Select/Select';
+import TextDynamicField from './components/Fields/Text/Text';
+import TextAreaDynamicField from './components/Fields/TextArea/TextArea';
+import {getContent, getFullPath} from './utils/common';
+
+import './FormGenerator.scss';
+
+const b = formGeneratorCn('form-generator');
+
+interface FormGeneratorProps {
+ blockConfig: Array;
+ contentConfig?: object;
+ onUpdateByKey?: (key: string, value: DynamicFormValue) => void;
+ onUpdate?: (value: object) => void;
+ className?: string;
+}
+
+export const FormGenerator = ({
+ blockConfig,
+ onUpdateByKey,
+ onUpdate,
+ contentConfig,
+}: FormGeneratorProps) => {
+ const inputs = blockConfig;
+
+ const onDataUpdate = React.useCallback(
+ (key: string, value: DynamicFormValue) => {
+ if (onUpdateByKey) {
+ onUpdateByKey(key, value);
+ }
+ if (onUpdate && contentConfig) {
+ const newContentConfig = _.cloneDeep(contentConfig);
+ _.set(newContentConfig, key, value);
+ onUpdate(newContentConfig);
+ }
+ },
+ [onUpdateByKey, onUpdate, contentConfig],
+ );
+
+ const getData = React.useCallback(
+ (variable: string) => {
+ if (variable.startsWith('block.')) {
+ const purePath = variable.replace('block.', '');
+ return _.get(contentConfig, purePath);
+ }
+
+ if (
+ (variable.startsWith(`'`) && variable.endsWith(`'`)) ||
+ (variable.startsWith(`"`) && variable.endsWith(`"`))
+ ) {
+ // @ts-ignore TODO: replaceAll types
+ return variable.replaceAll(`'`, '').replaceAll(`"`, '');
+ }
+
+ return undefined;
+ },
+ [contentConfig],
+ );
+
+ const decide = React.useCallback(
+ (showIf: string): boolean => {
+ const parts = showIf.split(' ');
+
+ if (!(parts.length === 3)) {
+ // eslint-disable-next-line no-console
+ console.log('Something bad happened in showIf, ignored');
+ return true;
+ }
+
+ const [firstVariable, equals, secondVariable] = parts;
+
+ const data1 = getData(firstVariable);
+ const data2 = getData(secondVariable);
+
+ if (equals === '===') {
+ return data1 === data2;
+ } else {
+ return data1 !== data2;
+ }
+ },
+ [getData],
+ );
+
+ const renderInput = React.useCallback(
+ (input: ConfigInput) => {
+ const fieldPath = input.name;
+ const fieldValue = getContent(contentConfig, input.name);
+
+ if (input.showIf) {
+ const decision = decide(input.showIf);
+
+ if (!decision) {
+ return Hidden Field: {input.name}
;
+ }
+ }
+
+ // Text, Select, Boolean and etc
+ const onSimpleDynamicFieldUpdate = (value: DynamicFormValue) => {
+ onDataUpdate(fieldPath, value);
+ };
+
+ // Array and Objects
+ const onComplexDynamicFieldUpdate = (key: string, value: DynamicFormValue) => {
+ onDataUpdate(getFullPath(fieldPath, key), value);
+ };
+
+ switch (input.type) {
+ case 'text': {
+ return (
+ onDataUpdate(fieldPath, value)}
+ title={input.title}
+ value={fieldValue}
+ onUpdate={onSimpleDynamicFieldUpdate}
+ />
+ );
+ }
+ case 'boolean': {
+ return (
+ onDataUpdate(fieldPath, value)}
+ title={input.title}
+ value={fieldValue}
+ onUpdate={onSimpleDynamicFieldUpdate}
+ />
+ );
+ }
+ case 'textarea': {
+ return (
+ onDataUpdate(fieldPath, value)}
+ title={input.title}
+ value={fieldValue}
+ onUpdate={onSimpleDynamicFieldUpdate}
+ />
+ );
+ }
+ case 'select': {
+ return (
+ onDataUpdate(fieldPath, value)}
+ input={input}
+ value={fieldValue}
+ onUpdate={onSimpleDynamicFieldUpdate}
+ />
+ );
+ }
+ case 'number': {
+ return (
+ onDataUpdate(fieldPath, value)}
+ title={input.title}
+ value={fieldValue}
+ onUpdate={onSimpleDynamicFieldUpdate}
+ />
+ );
+ }
+ case 'object': {
+ if (!input || !('properties' in input)) {
+ return null;
+ }
+
+ return (
+ onDataUpdate(fieldPath, value)}
+ blockConfig={input.properties}
+ title={input.title}
+ value={fieldValue}
+ onUpdate={onComplexDynamicFieldUpdate}
+ />
+ );
+ }
+ case 'array': {
+ return (
+
+ );
+ }
+ case 'oneOf': {
+ if (!input || !('options' in input)) {
+ return null;
+ }
+
+ return (
+
+ );
+ }
+ case 'anyOf': {
+ if (!input || !('options' in input)) {
+ return null;
+ }
+
+ return (
+
+ );
+ }
+ default: {
+ return Ignore {JSON.stringify(input)}
;
+ }
+ }
+ },
+ [contentConfig, decide, onDataUpdate],
+ );
+
+ const sortedInputs = inputs.sort((x, y) => {
+ const nestingFieldTypes = ['object', 'array', 'oneOf', 'anyOf'];
+ if (nestingFieldTypes.includes(x.type)) {
+ return 1;
+ }
+ if (nestingFieldTypes.includes(y.type)) {
+ return -1;
+ }
+ return 0;
+ });
+
+ return (
+
+ {sortedInputs.map((input, index) => (
+ {renderInput(input)}
+ ))}
+
+ );
+};
+
+export default FormGenerator;
diff --git a/src/form-generator/README.md b/src/form-generator/README.md
new file mode 100644
index 000000000..818cbee29
--- /dev/null
+++ b/src/form-generator/README.md
@@ -0,0 +1,161 @@
+# FormGenerator
+
+Компонент для динамической генерации форм на основе конфигурации. Поддерживает различные типы полей, вложенные структуры и условное отображение.
+
+## Подключение
+
+Импортируйте компонент в ваш файл:
+
+```typescript
+import {FormGenerator} from '@gravity-ui/page-constructor/form-generator';
+```
+
+## Пропсы
+
+Компонент принимает следующие пропсы:
+
+| Пропс | Тип | Описание |
+| --------------- | ------------------------------------------------ | --------------------------------------------------------------------------------- |
+| `blockConfig` | `Array` | Массив конфигураций полей формы |
+| `contentConfig` | `object` | Объект с текущими значениями полей |
+| `onUpdateByKey` | `(key: string, value: DynamicFormValue) => void` | Колбэк, вызываемый при изменении значения поля по ключу |
+| `onUpdate` | `(value: object) => void` | Колбэк, вызываемый при изменении любого поля, с обновленным объектом конфигурации |
+| `className` | `string` | Дополнительный CSS-класс для обертки формы |
+
+## Типы полей
+
+FormGenerator поддерживает следующие типы полей:
+
+- `text` - текстовое поле
+- `boolean` - переключатель
+- `textarea` - многострочное текстовое поле
+- `select` - выпадающий список или радиокнопки
+- `number` - числовое поле
+- `object` - вложенный объект с полями
+- `array` - массив значений
+- `oneOf` - выбор одного варианта из нескольких
+- `anyOf` - выбор нескольких вариантов из нескольких
+
+## Условное отображение
+
+Поле можно показывать/скрывать на основе значения другого поля с помощью параметра `showIf` в конфигурации поля:
+
+```typescript
+{
+ name: 'advancedOptions',
+ title: 'Расширенные настройки',
+ type: 'object',
+ properties: [...],
+ showIf: 'showAdvanced === true'
+}
+```
+
+В `showIf` можно использовать:
+
+- `block.` - для доступа к значениям других полей
+- Строковые литералы в кавычках
+- Операторы `===` и `!==`
+
+## Примеры использования
+
+### Базовое использование
+
+```tsx
+import * as React from 'react';
+import DynamicForm from '../../src/form-generator/FormGenerator';
+
+const MyForm = () => {
+ const [contentConfig, setContentConfig] = React.useState({});
+
+ const blockConfig = [
+ {
+ type: 'text',
+ name: 'title',
+ title: 'Заголовок',
+ },
+ {
+ type: 'number',
+ name: 'count',
+ title: 'Количество',
+ },
+ {
+ type: 'boolean',
+ name: 'isActive',
+ title: 'Активный',
+ },
+ ];
+
+ return (
+
+ );
+};
+```
+
+### Вложенные структуры
+
+```tsx
+const blockConfig = [
+ {
+ type: 'object',
+ name: 'style',
+ title: 'Стиль',
+ properties: [
+ {
+ type: 'text',
+ name: 'color',
+ title: 'Цвет',
+ },
+ {
+ type: 'number',
+ name: 'fontSize',
+ title: 'Размер шрифта',
+ },
+ ],
+ },
+];
+```
+
+### Массивы
+
+```tsx
+const blockConfig = [
+ {
+ type: 'array',
+ name: 'items',
+ title: 'Элементы',
+ arrayType: 'text',
+ buttonText: 'Добавить элемент',
+ },
+];
+```
+
+### Условное отображение
+
+```tsx
+const blockConfig = [
+ {
+ type: 'select',
+ name: 'styleType',
+ title: 'Тип стиля',
+ view: 'select',
+ mode: 'single',
+ enum: [
+ {value: 'simple', content: 'Простой'},
+ {value: 'advanced', content: 'Расширенный'},
+ ],
+ },
+ {
+ type: 'object',
+ name: 'advancedStyle',
+ title: 'Расширенные настройки',
+ properties: [
+ // ... поля
+ ],
+ showIf: 'styleType === "advanced"',
+ },
+];
+```
diff --git a/src/form-generator/components/FieldBase/FieldBase.scss b/src/form-generator/components/FieldBase/FieldBase.scss
new file mode 100644
index 000000000..569faa510
--- /dev/null
+++ b/src/form-generator/components/FieldBase/FieldBase.scss
@@ -0,0 +1,109 @@
+@import '../../styles/variables.scss';
+@import '../../styles/mixins.scss';
+
+$block: '.#{$ns-form-generator}field-base';
+
+#{$block} {
+ $class: &;
+ padding: 0;
+
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+
+ &:hover {
+ & > #{$class}__top #{$class}__title > #{$class}__button {
+ opacity: 1;
+ }
+ }
+
+ &_expandable {
+ flex-direction: column;
+
+ & > #{$block}__top {
+ width: 100%;
+ flex-basis: initial;
+ }
+
+ & > #{$block}__children {
+ width: 100%;
+ }
+ }
+
+ &__top {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ word-break: break-word;
+ }
+
+ &__children {
+ &_expandable {
+ border-left: 3px solid var(--g-color-line-generic);
+ padding: 6px 12px;
+ }
+ }
+
+ &__foldable {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ cursor: pointer;
+ padding: 6px 0;
+ // border: 3px solid var(--g-color-line-generic);
+ border-top-left-radius: var(--g-border-radius-m);
+ border-top-right-radius: var(--g-border-radius-m);
+
+ &:hover {
+ // border: 3px solid transparent;
+ color: var(--g-color-text-link-hover);
+ }
+
+ &:active {
+ color: var(--g-color-text-link-active);
+ }
+ }
+
+ &__non-foldable {
+ &:before {
+ //content: '•';
+ position: absolute;
+ right: calc(100% + 5px);
+ }
+ }
+
+ &__button {
+ transition: opacity 0.3s ease;
+ opacity: 0;
+ margin-left: 2px;
+ }
+
+ &__title {
+ @include text-body-2;
+ font-weight: 500;
+ margin: 5px 16px 5px 0;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ &_size {
+ &_s {
+ @include text-body-1;
+ }
+
+ &_m {
+ @include text-body-2;
+ }
+
+ &_l {
+ @include text-body-3;
+ }
+ }
+ }
+
+ &:last-child {
+ border-bottom: none;
+ }
+}
diff --git a/src/form-generator/components/FieldBase/FieldBase.tsx b/src/form-generator/components/FieldBase/FieldBase.tsx
new file mode 100644
index 000000000..08e946755
--- /dev/null
+++ b/src/form-generator/components/FieldBase/FieldBase.tsx
@@ -0,0 +1,83 @@
+import * as React from 'react';
+import {ArrowToggle, Button, Icon} from '@gravity-ui/uikit';
+import {ArrowRotateLeft} from '@gravity-ui/icons';
+import _ from 'lodash';
+
+import {formGeneratorCn} from '../../utils/cn';
+
+import './FieldBase.scss';
+
+const b = formGeneratorCn('field-base');
+
+export interface FieldBaseParams {
+ title?: string;
+ textSize?: 's' | 'm' | 'l';
+ onRefresh?: (value: undefined) => void;
+ expandable?: boolean;
+}
+
+export interface FieldBaseProps extends React.PropsWithChildren, FieldBaseParams {
+ className?: string;
+}
+
+const FieldBase: React.FC = ({
+ className,
+ title,
+ textSize = 's',
+ children,
+ expandable = false,
+ onRefresh,
+}) => {
+ const [showChildren, setShowChildren] = React.useState(!expandable);
+
+ const titleComponent = React.useMemo(() => {
+ if (title) {
+ const defaultTitle = (
+
+ {_.capitalize(title)}
+ {onRefresh && (
+ ) => {
+ e.stopPropagation();
+ onRefresh(undefined);
+ }}
+ view={'flat'}
+ size={'xs'}
+ >
+
+
+ )}
+
+ );
+
+ if (expandable) {
+ return (
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
+ setShowChildren(!showChildren)}>
+ {defaultTitle}
+
+
+ );
+ }
+
+ return {defaultTitle}
;
+ }
+
+ return null;
+ }, [expandable, showChildren, textSize, title, onRefresh]);
+
+ return (
+
+ {title &&
{titleComponent}
}
+ {(!title || showChildren) && (
+
{children}
+ )}
+
+ );
+};
+
+export default FieldBase;
diff --git a/src/form-generator/components/Fields/AnyOf/AnyOf.scss b/src/form-generator/components/Fields/AnyOf/AnyOf.scss
new file mode 100644
index 000000000..a53177753
--- /dev/null
+++ b/src/form-generator/components/Fields/AnyOf/AnyOf.scss
@@ -0,0 +1,14 @@
+@import '../../../styles/variables.scss';
+@import '../../../styles/mixins.scss';
+
+$block: '.#{$ns-form-generator}anyof-dynamic-field';
+
+#{$block} {
+ &__radio {
+ margin: 16px 12px;
+ }
+
+ &__select {
+ min-width: 80px;
+ }
+}
diff --git a/src/form-generator/components/Fields/AnyOf/AnyOf.tsx b/src/form-generator/components/Fields/AnyOf/AnyOf.tsx
new file mode 100644
index 000000000..a33b4d7c6
--- /dev/null
+++ b/src/form-generator/components/Fields/AnyOf/AnyOf.tsx
@@ -0,0 +1,92 @@
+import {Card, SegmentedRadioGroup, Select} from '@gravity-ui/uikit';
+import * as React from 'react';
+
+import {AnyOfInput, DynamicFormValue} from '../../../types';
+import {formGeneratorCn} from '../../../utils/cn';
+import DynamicForm from '../../../FormGenerator';
+import FieldBase from '../../FieldBase/FieldBase';
+
+import './AnyOf.scss';
+
+const b = formGeneratorCn('anyof-dynamic-field');
+
+interface AnyOfDynamicFieldProps {
+ contentConfig: DynamicFormValue;
+ onUpdate: (key: string, value: DynamicFormValue) => void;
+ inputConfig: AnyOfInput;
+ className?: string;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const getAnyOfContentConfig = (contentConfig: any, name: string) => {
+ if (name) {
+ return contentConfig ? contentConfig[name] : {};
+ }
+ return contentConfig;
+};
+
+const AnyOfDynamicField = ({
+ contentConfig,
+ onUpdate,
+ className,
+ inputConfig,
+}: AnyOfDynamicFieldProps) => {
+ const defaultValue = inputConfig.options[0].value;
+
+ const [anyOfMetaValue, setAnyOfMetaValue] = React.useState(defaultValue);
+
+ const anyOfContentConfig = getAnyOfContentConfig(contentConfig, inputConfig.name);
+ const anyOfChosenOption = React.useMemo(
+ () =>
+ inputConfig.options.find(
+ ({value: foundAnyOfValue}) => foundAnyOfValue === anyOfMetaValue,
+ ),
+ [inputConfig.options, anyOfMetaValue],
+ );
+
+ const onUpdateAnyOf = React.useCallback((value: string) => {
+ setAnyOfMetaValue(value);
+ }, []);
+
+ return (
+ onUpdate('', value)}
+ expandable
+ >
+
+ {inputConfig.options.length < 4 ? (
+ ({
+ content: option.title,
+ value: option.value,
+ }))}
+ value={anyOfMetaValue}
+ onUpdate={onUpdateAnyOf}
+ />
+ ) : (
+ ({
+ content: option.title,
+ value: option.value,
+ }))}
+ value={anyOfMetaValue ? [anyOfMetaValue] : []}
+ onUpdate={([selectValue]) => onUpdateAnyOf(selectValue)}
+ className={b('select')}
+ />
+ )}
+ {anyOfChosenOption && (
+
+ )}
+
+
+ );
+};
+
+export default AnyOfDynamicField;
diff --git a/src/form-generator/components/Fields/Array/Array.scss b/src/form-generator/components/Fields/Array/Array.scss
new file mode 100644
index 000000000..4118d3a47
--- /dev/null
+++ b/src/form-generator/components/Fields/Array/Array.scss
@@ -0,0 +1,52 @@
+@import '../../../styles/variables.scss';
+@import '../../../styles/mixins.scss';
+
+$block: '.#{$ns-form-generator}array-dynamic-field';
+
+#{$block} {
+ &__card {
+ padding: 12px;
+ margin-top: 12px;
+ border-left: 3px solid var(--g-color-line-generic);
+
+ &:first-child {
+ margin-top: 0;
+ }
+ }
+
+ &__row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+
+ margin-top: 10px;
+
+ &:first-child {
+ margin-top: 0;
+ }
+ }
+
+ &__card-head {
+ margin-bottom: 8px;
+ }
+
+ &__row-title {
+ @include text-subheader-3;
+ }
+
+ &__row-title,
+ &__row-field {
+ flex: 1;
+ }
+
+ &__empty {
+ padding: 10px;
+ display: flex;
+ justify-content: center;
+ }
+
+ &__add-button {
+ margin-top: 12px;
+ }
+}
diff --git a/src/form-generator/components/Fields/Array/Array.tsx b/src/form-generator/components/Fields/Array/Array.tsx
new file mode 100644
index 000000000..07bccea4d
--- /dev/null
+++ b/src/form-generator/components/Fields/Array/Array.tsx
@@ -0,0 +1,154 @@
+import {Plus} from '@gravity-ui/icons';
+import {Button, Icon} from '@gravity-ui/uikit';
+import * as React from 'react';
+
+import {ArrayObjectInput, ArrayTextInput, DynamicFormValue} from '../../../types';
+import {removeFromArray, swapArrayItems} from '../../../../editor-v2/utils';
+import {formGeneratorCn} from '../../../utils/cn';
+import DynamicForm from '../../../FormGenerator';
+import FieldBase from '../../FieldBase/FieldBase';
+import Text from '../Text/Text';
+import ItemButton from './ItemButton/ItemButton';
+
+import './Array.scss';
+
+const b = formGeneratorCn('array-dynamic-field');
+
+type ArrayInput = ArrayTextInput | ArrayObjectInput;
+
+interface ArrayFieldProps {
+ title: string;
+ values: Array;
+ onUpdate: (key: string, value: DynamicFormValue) => void;
+ blockConfig: ArrayInput;
+ className?: string;
+}
+
+const ArrayDynamicField = ({title, values, onUpdate, className, blockConfig}: ArrayFieldProps) => {
+ const haveItems = values && Array.isArray(values) && values.length;
+
+ const onAddItem = React.useCallback(() => {
+ if (blockConfig.arrayType === 'text') {
+ onUpdate('', haveItems ? [...values, ''] : ['']);
+ } else if (blockConfig.arrayType === 'object') {
+ onUpdate('', haveItems ? [...values, {}] : [{}]);
+ }
+ }, [blockConfig.arrayType, haveItems, onUpdate, values]);
+
+ const onDeleteItem = React.useCallback(
+ (index: number) => {
+ if (Array.isArray(values)) {
+ const newArray = removeFromArray(values, index);
+ onUpdate('', newArray);
+ }
+ },
+ [onUpdate, values],
+ );
+
+ const onReorderItem = React.useCallback(
+ (index: number, placement: 'up' | 'down') => {
+ if (Array.isArray(values)) {
+ const newArray = swapArrayItems(
+ values,
+ index,
+ placement === 'up' ? index - 1 : index + 1,
+ );
+ onUpdate('', newArray);
+ }
+ },
+ [onUpdate, values],
+ );
+
+ const renderInput = React.useCallback(
+ (value: DynamicFormValue, index: number) => {
+ const arrayItemButton = (
+ onDeleteItem(index)}
+ onReorderUp={() => onReorderItem(index, 'up')}
+ onReorderDown={() => onReorderItem(index, 'down')}
+ disableReorderUp={index === 0}
+ disableReorderDown={Boolean(haveItems) && values.length === index + 1}
+ />
+ );
+
+ switch (blockConfig.arrayType) {
+ case 'text': {
+ return (
+
+ onUpdate(`[${index}]`, updateValue)}
+ onRefresh={(updatedValue) => onUpdate('', updatedValue)}
+ />
+ {arrayItemButton}
+
+ );
+ }
+ case 'object': {
+ if (!blockConfig.properties) {
+ return null;
+ }
+ return (
+
+
+
Item {index + 1}
+ {arrayItemButton}
+
+
+ onUpdate(`[${index}].${key}`, updateValue)
+ }
+ />
+
+ );
+ }
+ default: {
+ return null;
+ }
+ }
+ },
+ [blockConfig, haveItems, onDeleteItem, onReorderItem, onUpdate, values],
+ );
+
+ const renderInputs = React.useCallback(() => {
+ if (haveItems) {
+ const renderItems = values
+ .map(renderInput)
+ .filter(Boolean) as unknown as React.ReactNode[];
+ return (
+
+ {renderItems}
+
+
+ {blockConfig.buttonText}
+
+
+ );
+ } else {
+ return (
+
+
+
+ Please, add new item
+
+
+ );
+ }
+ }, [blockConfig.buttonText, haveItems, onAddItem, renderInput, values]);
+
+ return (
+ onUpdate('', value)}
+ expandable
+ >
+ {renderInputs()}
+
+ );
+};
+
+export default ArrayDynamicField;
diff --git a/src/form-generator/components/Fields/Array/ItemButton/ItemButton.tsx b/src/form-generator/components/Fields/Array/ItemButton/ItemButton.tsx
new file mode 100644
index 000000000..a8453d400
--- /dev/null
+++ b/src/form-generator/components/Fields/Array/ItemButton/ItemButton.tsx
@@ -0,0 +1,75 @@
+import {ArrowDown, ArrowUp, EllipsisVertical, TrashBin} from '@gravity-ui/icons';
+import {Button, Icon, Menu, Popup} from '@gravity-ui/uikit';
+import * as React from 'react';
+
+import {formGeneratorCn} from '../../../../utils/cn';
+
+const b = formGeneratorCn('array-item-button');
+
+interface ItemButtonProps {
+ onRemove: () => void;
+ onReorderUp: () => void;
+ disableReorderUp?: boolean;
+ onReorderDown: () => void;
+ disableReorderDown?: boolean;
+ className?: string;
+}
+
+const ItemButton = ({
+ className,
+ onRemove,
+ onReorderUp,
+ onReorderDown,
+ disableReorderUp = false,
+ disableReorderDown = false,
+}: ItemButtonProps) => {
+ const buttonRef = React.useRef(null);
+ const [isOpen, setIsOpen] = React.useState(false);
+
+ const onMenuItemClickWrapper = React.useCallback((callback: () => void) => {
+ return () => {
+ setIsOpen(false);
+ callback();
+ };
+ }, []);
+
+ return (
+
+ setIsOpen(true)}>
+
+
+ setIsOpen(false)}
+ >
+
+ }
+ >
+ Remove
+
+ }
+ >
+ Reorder Up
+
+ }
+ >
+ Reorder Down
+
+
+
+
+ );
+};
+
+export default ItemButton;
diff --git a/src/form-generator/components/Fields/Boolean/Boolean.scss b/src/form-generator/components/Fields/Boolean/Boolean.scss
new file mode 100644
index 000000000..26d495fc2
--- /dev/null
+++ b/src/form-generator/components/Fields/Boolean/Boolean.scss
@@ -0,0 +1,10 @@
+@import '../../../styles/variables.scss';
+@import '../../../styles/mixins.scss';
+
+$block: '.#{$ns-form-generator}boolean-dynamic-field';
+
+#{$block} {
+ &__switch {
+ margin-top: 5px;
+ }
+}
diff --git a/src/form-generator/components/Fields/Boolean/Boolean.tsx b/src/form-generator/components/Fields/Boolean/Boolean.tsx
new file mode 100644
index 000000000..870d80fe6
--- /dev/null
+++ b/src/form-generator/components/Fields/Boolean/Boolean.tsx
@@ -0,0 +1,24 @@
+import {Switch} from '@gravity-ui/uikit';
+
+import {formGeneratorCn} from '../../../utils/cn';
+import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase';
+
+import './Boolean.scss';
+
+const b = formGeneratorCn('boolean-dynamic-field');
+
+interface BooleanProps extends FieldBaseParams {
+ value: string;
+ onUpdate: (value: boolean | undefined) => void;
+ className?: string;
+}
+
+const BooleanDynamicField = ({title, value, onUpdate, className}: BooleanProps) => {
+ return (
+
+
+
+ );
+};
+
+export default BooleanDynamicField;
diff --git a/src/form-generator/components/Fields/Number/Number.tsx b/src/form-generator/components/Fields/Number/Number.tsx
new file mode 100644
index 000000000..1f56843bd
--- /dev/null
+++ b/src/form-generator/components/Fields/Number/Number.tsx
@@ -0,0 +1,26 @@
+import {TextInput} from '@gravity-ui/uikit';
+
+import {formGeneratorCn} from '../../../utils/cn';
+import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase';
+
+const b = formGeneratorCn('number-dynamic-field');
+
+interface NumberDynamicFieldProps extends FieldBaseParams {
+ value: string;
+ onUpdate: (value: number | undefined) => void;
+ className?: string;
+}
+
+const NumberDynamicField = ({title, value, onUpdate, className}: NumberDynamicFieldProps) => {
+ const onUpdateFunc = (updateValue: string) => {
+ onUpdate(Number(updateValue));
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default NumberDynamicField;
diff --git a/src/form-generator/components/Fields/Object/Object.tsx b/src/form-generator/components/Fields/Object/Object.tsx
new file mode 100644
index 000000000..7f0305f38
--- /dev/null
+++ b/src/form-generator/components/Fields/Object/Object.tsx
@@ -0,0 +1,31 @@
+import {ConfigInput, DynamicFormValue} from '../../../types';
+import DynamicForm from '../../../FormGenerator';
+import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase';
+
+interface ObjectDynamicFieldProps extends FieldBaseParams {
+ value: object;
+ onUpdate: (key: string, value: DynamicFormValue) => void;
+ blockConfig: Array;
+ className?: string;
+}
+
+const ObjectDynamicField = ({
+ title,
+ value,
+ onUpdate,
+ className,
+ blockConfig,
+}: ObjectDynamicFieldProps) => {
+ return (
+ onUpdate('', updatedValue)}
+ expandable
+ >
+
+
+ );
+};
+
+export default ObjectDynamicField;
diff --git a/src/form-generator/components/Fields/OneOf/OneOf.scss b/src/form-generator/components/Fields/OneOf/OneOf.scss
new file mode 100644
index 000000000..086fe149b
--- /dev/null
+++ b/src/form-generator/components/Fields/OneOf/OneOf.scss
@@ -0,0 +1,14 @@
+@import '../../../styles/variables.scss';
+@import '../../../styles/mixins.scss';
+
+$block: '.#{$ns-form-generator}oneof-dynamic-field';
+
+#{$block} {
+ &__radio {
+ margin-bottom: 12px;
+ }
+
+ &__select {
+ min-width: 80px;
+ }
+}
diff --git a/src/form-generator/components/Fields/OneOf/OneOf.tsx b/src/form-generator/components/Fields/OneOf/OneOf.tsx
new file mode 100644
index 000000000..9d331fd97
--- /dev/null
+++ b/src/form-generator/components/Fields/OneOf/OneOf.tsx
@@ -0,0 +1,90 @@
+import {SegmentedRadioGroup, Select} from '@gravity-ui/uikit';
+import * as React from 'react';
+
+import {DynamicFormValue, OneOfInput} from '../../../types';
+import {formGeneratorCn} from '../../../utils/cn';
+import DynamicForm from '../../../FormGenerator';
+import FieldBase from '../../FieldBase/FieldBase';
+
+import './OneOf.scss';
+
+const b = formGeneratorCn('oneof-dynamic-field');
+
+interface OneOfDynamicFieldProps {
+ contentConfig: DynamicFormValue;
+ onUpdate: (key: string, value: DynamicFormValue) => void;
+ inputConfig: OneOfInput;
+ className?: string;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const getOneOfContentConfig = (contentConfig: any, name: string) => {
+ if (name) {
+ return contentConfig ? contentConfig[name] : {};
+ }
+ return contentConfig;
+};
+
+const OneOfDynamicField = ({
+ contentConfig,
+ onUpdate,
+ className,
+ inputConfig,
+}: OneOfDynamicFieldProps) => {
+ const defaultValue = inputConfig.options[0].value;
+
+ const [oneOfMetaValue, setOneOfMetaValue] = React.useState(defaultValue);
+
+ const oneOfContentConfig = getOneOfContentConfig(contentConfig, inputConfig.name);
+ const oneOfChosenOption = React.useMemo(
+ () =>
+ inputConfig.options.find(
+ ({value: foundOneOfValue}) => foundOneOfValue === oneOfMetaValue,
+ ),
+ [inputConfig.options, oneOfMetaValue],
+ );
+
+ const onUpdateOneOf = React.useCallback((value: string) => {
+ setOneOfMetaValue(value);
+ }, []);
+
+ return (
+ onUpdate('', value)}
+ expandable
+ >
+ {inputConfig.options.length < 4 ? (
+ ({
+ content: option.title,
+ value: option.value,
+ }))}
+ value={oneOfMetaValue}
+ onUpdate={onUpdateOneOf}
+ />
+ ) : (
+ ({
+ content: option.title,
+ value: option.value,
+ }))}
+ value={oneOfMetaValue ? [oneOfMetaValue] : []}
+ onUpdate={([selectValue]) => onUpdateOneOf(selectValue)}
+ className={b('select')}
+ />
+ )}
+ {oneOfChosenOption && (
+
+ )}
+
+ );
+};
+
+export default OneOfDynamicField;
diff --git a/src/form-generator/components/Fields/Select/Select.scss b/src/form-generator/components/Fields/Select/Select.scss
new file mode 100644
index 000000000..c131b7345
--- /dev/null
+++ b/src/form-generator/components/Fields/Select/Select.scss
@@ -0,0 +1,10 @@
+@import '../../../styles/variables.scss';
+@import '../../../styles/mixins.scss';
+
+$block: '.#{$ns-form-generator}editor-select-field';
+
+#{$block} {
+ &__select {
+ min-width: 80px;
+ }
+}
diff --git a/src/form-generator/components/Fields/Select/Select.tsx b/src/form-generator/components/Fields/Select/Select.tsx
new file mode 100644
index 000000000..d3a57e752
--- /dev/null
+++ b/src/form-generator/components/Fields/Select/Select.tsx
@@ -0,0 +1,50 @@
+import {SegmentedRadioGroup, Select} from '@gravity-ui/uikit';
+
+import {SelectMultipleInput, SelectSingleInput} from '../../../types';
+import {formGeneratorCn} from '../../../utils/cn';
+import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase';
+
+import './Select.scss';
+
+const b = formGeneratorCn('editor-select-field');
+
+type SelectInput = SelectSingleInput | SelectMultipleInput;
+
+interface SelectDynamicFieldProps extends FieldBaseParams {
+ input: SelectInput;
+ value: string | string[];
+ onUpdate: (value: string | string[] | undefined) => void;
+ className?: string;
+}
+
+const SelectDynamicField = ({input, value, onUpdate, className}: SelectDynamicFieldProps) => {
+ const inputView = input.view || 'radiobutton';
+ const isMultiple = input.mode === 'multiple';
+ const currentValue = Array.isArray(value) ? value : [value];
+
+ return (
+
+ {(inputView === 'select' || isMultiple) && (
+
+ isMultiple ? onUpdate(selectValues) : onUpdate(selectValues[0])
+ }
+ options={input.enum}
+ className={b('select')}
+ multiple={isMultiple}
+ />
+ )}
+ {inputView === 'radiobutton' && !isMultiple && (
+
+ )}
+
+ );
+};
+
+export default SelectDynamicField;
diff --git a/src/form-generator/components/Fields/Text/Text.tsx b/src/form-generator/components/Fields/Text/Text.tsx
new file mode 100644
index 000000000..c3c6cf5eb
--- /dev/null
+++ b/src/form-generator/components/Fields/Text/Text.tsx
@@ -0,0 +1,22 @@
+import {TextInput} from '@gravity-ui/uikit';
+
+import {formGeneratorCn} from '../../../utils/cn';
+import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase';
+
+const b = formGeneratorCn('text-dynamic-field');
+
+interface TextDynamicFieldProps extends FieldBaseParams {
+ value: string;
+ onUpdate: (value: string | undefined) => void;
+ className?: string;
+}
+
+const TextDynamicField = ({title, value, onUpdate, className}: TextDynamicFieldProps) => {
+ return (
+
+
+
+ );
+};
+
+export default TextDynamicField;
diff --git a/src/form-generator/components/Fields/TextArea/TextArea.tsx b/src/form-generator/components/Fields/TextArea/TextArea.tsx
new file mode 100644
index 000000000..7676ed8a4
--- /dev/null
+++ b/src/form-generator/components/Fields/TextArea/TextArea.tsx
@@ -0,0 +1,22 @@
+import {TextArea} from '@gravity-ui/uikit';
+
+import {formGeneratorCn} from '../../../utils/cn';
+import FieldBase, {FieldBaseParams} from '../../FieldBase/FieldBase';
+
+const b = formGeneratorCn('textarea-dynamic-field');
+
+interface TextAreaDynamicFieldProps extends FieldBaseParams {
+ value: string;
+ onUpdate: (value: string | undefined) => void;
+ className?: string;
+}
+
+const TextAreaDynamicField = ({title, value, onUpdate, className}: TextAreaDynamicFieldProps) => {
+ return (
+
+
+
+ );
+};
+
+export default TextAreaDynamicField;
diff --git a/src/form-generator/index.ts b/src/form-generator/index.ts
new file mode 100644
index 000000000..77eb31cf8
--- /dev/null
+++ b/src/form-generator/index.ts
@@ -0,0 +1,2 @@
+export * from './FormGenerator';
+export * from './types';
diff --git a/src/form-generator/styles/mixins.scss b/src/form-generator/styles/mixins.scss
new file mode 100644
index 000000000..bb482d864
--- /dev/null
+++ b/src/form-generator/styles/mixins.scss
@@ -0,0 +1 @@
+@import '../../../styles/mixins.scss';
diff --git a/src/form-generator/styles/variables.scss b/src/form-generator/styles/variables.scss
new file mode 100644
index 000000000..9a94ae304
--- /dev/null
+++ b/src/form-generator/styles/variables.scss
@@ -0,0 +1 @@
+$ns-form-generator: 'pcformgenerator-';
diff --git a/src/form-generator/types.ts b/src/form-generator/types.ts
new file mode 100644
index 000000000..23bfb8592
--- /dev/null
+++ b/src/form-generator/types.ts
@@ -0,0 +1,136 @@
+import {PageContentWithNavigation} from '../models';
+
+export type DynamicFormValue =
+ | string
+ | number
+ | []
+ | object
+ | boolean
+ | PageContentWithNavigation
+ | undefined;
+
+export interface BlockConfig {
+ name: string;
+ inputs: Array;
+ group?: string;
+ default?: object;
+ previewImg?: string;
+}
+
+export interface TextInput {
+ type: 'text';
+ name: string;
+ title: string;
+}
+
+export interface BooleanInput {
+ type: 'boolean';
+ name: string;
+ title: string;
+}
+
+export interface NumberInput {
+ type: 'number';
+ name: string;
+ title: string;
+}
+
+export interface TextAreaInput {
+ type: 'textarea';
+ name: string;
+ title: string;
+}
+
+export interface SelectBaseInput {
+ type: 'select';
+ name: string;
+ title: string;
+ view: 'select' | 'radiobutton';
+ mode: 'single' | 'multiple';
+ enum: Array<{content: string; value: string}>;
+}
+
+export interface SelectSingleInput extends SelectBaseInput {
+ type: 'select';
+ name: string;
+ title: string;
+ view: 'select' | 'radiobutton';
+ mode: 'single';
+ enum: Array<{content: string; value: string}>;
+}
+
+export interface SelectMultipleInput extends SelectBaseInput {
+ type: 'select';
+ name: string;
+ title: string;
+ view: 'select';
+ mode: 'multiple';
+ enum: Array<{content: string; value: string}>;
+}
+
+export interface ObjectInput {
+ type: 'object';
+ name: string;
+ title: string;
+ properties: Array;
+}
+
+export interface ArrayBaseInput {
+ type: 'array';
+ arrayType: 'object' | 'text';
+ name: string;
+ title: string;
+ buttonText: string;
+}
+
+export interface ArrayTextInput extends ArrayBaseInput {
+ arrayType: 'text';
+}
+
+export interface ArrayObjectInput extends ArrayBaseInput {
+ arrayType: 'object';
+ properties: Array;
+}
+
+export interface OneOfInput {
+ type: 'oneOf';
+ name: string;
+ key?: string;
+ title: string;
+ options: {
+ value: string;
+ title: string;
+ properties: Array;
+ }[];
+}
+
+export interface AnyOfInput {
+ type: 'anyOf';
+ name: string;
+ key?: string;
+ title: string;
+ options: {
+ value: string;
+ title: string;
+ properties: Array;
+ }[];
+}
+
+export interface GeneralProps {
+ showIf?: string;
+}
+
+export type ConfigInput = (
+ | TextInput
+ | BooleanInput
+ | NumberInput
+ | TextAreaInput
+ | SelectSingleInput
+ | SelectMultipleInput
+ | ObjectInput
+ | ArrayTextInput
+ | ArrayObjectInput
+ | OneOfInput
+ | AnyOfInput
+) &
+ GeneralProps;
diff --git a/src/form-generator/utils/cn.ts b/src/form-generator/utils/cn.ts
new file mode 100644
index 000000000..f8ff60f15
--- /dev/null
+++ b/src/form-generator/utils/cn.ts
@@ -0,0 +1,5 @@
+import {withNaming} from '@bem-react/classname';
+
+export const FORM_GENERATOR_NAMESPACE = 'pcformgenerator-';
+
+export const formGeneratorCn = withNaming({n: FORM_GENERATOR_NAMESPACE, e: '__', m: '_'});
diff --git a/src/form-generator/utils/common.ts b/src/form-generator/utils/common.ts
new file mode 100644
index 000000000..ac4a098bb
--- /dev/null
+++ b/src/form-generator/utils/common.ts
@@ -0,0 +1,23 @@
+import _ from 'lodash';
+
+import {DynamicFormValue} from '../types';
+
+export const getFullPath = (path: string, name: string) => {
+ if (!path && !name) {
+ return '';
+ }
+
+ if (!path) {
+ return name;
+ }
+
+ if (!name) {
+ return path;
+ }
+
+ return path + '.' + name;
+};
+
+export const getContent = (contentConfig: DynamicFormValue, path: string) => {
+ return path ? _.get(contentConfig, path) : contentConfig;
+};
diff --git a/src/grid/Grid/Grid.scss b/src/grid/Grid/Grid.scss
index 5c172d42f..6097bcc48 100644
--- a/src/grid/Grid/Grid.scss
+++ b/src/grid/Grid/Grid.scss
@@ -15,8 +15,8 @@ $block: '.#{$ns}Grid';
}
.row {
- margin-right: 0;
- margin-left: 0;
+ //margin-right: 0;
+ //margin-left: 0;
}
#{$block} {
@@ -26,7 +26,7 @@ $block: '.#{$ns}Grid';
}
}
- .row .row {
+ .row {
margin: 0 -#{$gutter};
}
@@ -58,7 +58,7 @@ $block: '.#{$ns}Grid';
}
}
- .row .row {
+ .row {
margin: 0 -#{$gutterMobile};
}
}
diff --git a/src/hooks/useAnalytics.ts b/src/hooks/useAnalytics.ts
index 5eb1768d9..7212ef646 100644
--- a/src/hooks/useAnalytics.ts
+++ b/src/hooks/useAnalytics.ts
@@ -12,7 +12,7 @@ export const useAnalytics = (name = '', target?: string) => {
name
? {
name,
- context,
+ context: String(context),
type: PredefinedEventTypes.Default,
target: target,
}
diff --git a/src/hooks/usePCEditorBlockMouseEvents.ts b/src/hooks/usePCEditorBlockMouseEvents.ts
new file mode 100644
index 000000000..303d55592
--- /dev/null
+++ b/src/hooks/usePCEditorBlockMouseEvents.ts
@@ -0,0 +1,67 @@
+import * as React from 'react';
+
+import _ from 'lodash';
+
+import {getCursorPositionOverElement} from '../utils/editor';
+
+import {sendEventPostMessage} from './usePostMessageAPI';
+
+const usePCEditorBlockMouseEvents = (arrayIndex: number[], element?: HTMLElement) => {
+ const onMouseUp = React.useCallback(
+ (e: React.MouseEvent) => {
+ e.stopPropagation();
+ if (element) {
+ const rect = element.getClientRects().item(0);
+ if (rect) {
+ const position = getCursorPositionOverElement(rect, e);
+ sendEventPostMessage('ON_MOUSE_UP', {path: arrayIndex, rect, position});
+ }
+ }
+ },
+ [arrayIndex, element],
+ );
+
+ const onMouseMove = React.useCallback(
+ (e: React.MouseEvent) => {
+ e.stopPropagation();
+ if (element) {
+ const rect = element.getClientRects().item(0);
+ if (rect) {
+ const position = getCursorPositionOverElement(rect, e);
+ sendEventPostMessage('ON_HOVER_BLOCK', {rect, position});
+ sendEventPostMessage('ON_MOUSE_MOVE', {x: e.clientX, y: e.clientY});
+ }
+ }
+ },
+ [element],
+ );
+
+ const onMouseLeave = React.useCallback(() => {
+ if (element) {
+ const rect = element.getClientRects().item(0);
+ if (rect) {
+ sendEventPostMessage('ON_HOVER_BLOCK', {});
+ }
+ }
+ }, [element]);
+
+ const onClick = React.useCallback(
+ (e: React.MouseEvent) => {
+ e.stopPropagation();
+ if (element) {
+ sendEventPostMessage('ON_CLICK_BLOCK', {path: arrayIndex});
+ }
+ },
+ [arrayIndex, element],
+ );
+
+ return {
+ onClick,
+ onMouseMove,
+ onMouseUp,
+ onMouseLeave,
+ 'data-editor-item': true,
+ };
+};
+
+export default usePCEditorBlockMouseEvents;
diff --git a/src/hooks/usePCEditorBlockSelection.ts b/src/hooks/usePCEditorBlockSelection.ts
new file mode 100644
index 000000000..eb934b92c
--- /dev/null
+++ b/src/hooks/usePCEditorBlockSelection.ts
@@ -0,0 +1,33 @@
+import * as React from 'react';
+import _ from 'lodash';
+
+import {usePCEditorStore} from './usePCEditorStore';
+import {sendEventPostMessage} from './usePostMessageAPI';
+
+const usePCEditorBlockSelection = (arrayIndex: number[], element?: HTMLElement) => {
+ const {selectedBlock} = usePCEditorStore();
+
+ const onResize = React.useCallback(() => {
+ if (element && _.isEqual(selectedBlock, arrayIndex)) {
+ const rect = element.getClientRects().item(0);
+ if (rect) {
+ sendEventPostMessage('ON_UPDATE_BLOCK_SELECTION', {rect});
+ }
+ }
+ }, [arrayIndex, element, selectedBlock]);
+
+ React.useEffect(() => {
+ window.addEventListener('resize', onResize);
+
+ return () => {
+ window.removeEventListener('resize', onResize);
+ };
+ }, [element, onResize]);
+
+ // Update blockBorders when selectedBlock changes
+ React.useEffect(() => {
+ onResize();
+ }, [onResize]);
+};
+
+export default usePCEditorBlockSelection;
diff --git a/src/hooks/usePCEditorInitializeEvents.ts b/src/hooks/usePCEditorInitializeEvents.ts
new file mode 100644
index 000000000..0b6990d6f
--- /dev/null
+++ b/src/hooks/usePCEditorInitializeEvents.ts
@@ -0,0 +1,85 @@
+import * as React from 'react';
+
+import {JSONSchemaType} from 'ajv';
+import _ from 'lodash';
+
+import {ItemConfig} from '../common/types';
+import {blockDataMap} from '../constructor-items';
+import {PageContentWithNavigation} from '../models';
+import {defaultComponentsConfigurationSchema} from '../schema';
+import {generateFromAJV} from '../utils/form-generator';
+
+import {usePCEditorStore} from './usePCEditorStore';
+import {sendEventPostMessage, useInternalPostMessageAPIListener} from './usePostMessageAPI';
+
+interface UseEditorInitializeProps {
+ initialContent: PageContentWithNavigation;
+ setContent: (content: PageContentWithNavigation) => void;
+}
+
+export const usePCEditorInitializeEvents = ({
+ initialContent,
+ setContent,
+}: UseEditorInitializeProps) => {
+ const {manipulateOverlayMode, initialized, content} = usePCEditorStore();
+
+ React.useEffect(() => {
+ if (initialized) {
+ setContent(content);
+ }
+ }, [content, initialized, setContent]);
+
+ useInternalPostMessageAPIListener('GET_INITIAL_CONTENT', () => {
+ sendEventPostMessage('ON_INITIAL_CONTENT', initialContent);
+ });
+
+ useInternalPostMessageAPIListener('GET_SUPPORTED_BLOCKS', () => {
+ sendEventPostMessage('ON_SUPPORTED_BLOCKS', {
+ blocks: Object.entries(blockDataMap).reduce((acc, [key, value]) => {
+ acc.push({type: key, schema: value.schema});
+ return acc;
+ }, [] as ItemConfig[]),
+ subBlocks: [],
+ global: generateFromAJV(
+ defaultComponentsConfigurationSchema as unknown as JSONSchemaType<{}>,
+ ),
+ });
+ });
+
+ const onResize = React.useCallback(() => {
+ const height = document.documentElement.getBoundingClientRect().height;
+ sendEventPostMessage('ON_RESIZE', {height});
+ }, []);
+
+ new ResizeObserver(onResize).observe(document.body);
+
+ React.useEffect(() => {
+ const onMouseUp = () => {
+ sendEventPostMessage('ON_MOUSE_UP', {});
+ };
+
+ const onMouseMove = (event: MouseEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+ sendEventPostMessage('ON_MOUSE_MOVE', {x: event.clientX, y: event.clientY});
+ };
+
+ const throttleOnMouseMove = _.throttle(onMouseMove, 10);
+ const throttleOnMouseUp = _.throttle(onMouseUp, 10);
+
+ document.addEventListener('mousemove', throttleOnMouseMove);
+ document.addEventListener('mouseup', throttleOnMouseUp);
+ window.addEventListener('resize', onResize);
+
+ return () => {
+ document.removeEventListener('mousemove', throttleOnMouseMove);
+ document.removeEventListener('mouseup', throttleOnMouseUp);
+ window.removeEventListener('resize', onResize);
+ };
+ }, [manipulateOverlayMode, onResize]);
+
+ React.useEffect(() => {
+ const height = document.documentElement.getBoundingClientRect().height;
+ sendEventPostMessage('ON_INIT', {height});
+ }, []);
+};
diff --git a/src/hooks/usePCEditorItemWrap.ts b/src/hooks/usePCEditorItemWrap.ts
new file mode 100644
index 000000000..eb7eb55a8
--- /dev/null
+++ b/src/hooks/usePCEditorItemWrap.ts
@@ -0,0 +1,22 @@
+import * as React from 'react';
+
+import {BlockIdContext} from '../context/blockIdContext';
+
+import usePCEditorBlockMouseEvents from './usePCEditorBlockMouseEvents';
+import usePCEditorBlockSelection from './usePCEditorBlockSelection';
+
+export function usePCEditorItemWrap(index = 0) {
+ const [element, setElement] = React.useState();
+
+ const blockRef = React.useCallback((node: HTMLElement | null) => {
+ if (node !== null) {
+ setElement(node);
+ }
+ }, []);
+
+ const parentBlockId = React.useContext(BlockIdContext);
+ const adminBlockMouseEvents = usePCEditorBlockMouseEvents([...parentBlockId, index], element);
+ usePCEditorBlockSelection([...parentBlockId, index], element);
+
+ return {adminBlockMouseEvents, blockRef};
+}
diff --git a/src/hooks/usePCEditorStore.ts b/src/hooks/usePCEditorStore.ts
new file mode 100644
index 000000000..6e70148c9
--- /dev/null
+++ b/src/hooks/usePCEditorStore.ts
@@ -0,0 +1,10 @@
+import * as React from 'react';
+
+import {useStore} from 'zustand';
+
+import {PCEditorStoreContext} from '../context/editorStoreContext';
+
+export const usePCEditorStore = () => {
+ const {state} = React.useContext(PCEditorStoreContext);
+ return useStore(state);
+};
diff --git a/src/hooks/usePostMessageAPI.ts b/src/hooks/usePostMessageAPI.ts
new file mode 100644
index 000000000..346828b6e
--- /dev/null
+++ b/src/hooks/usePostMessageAPI.ts
@@ -0,0 +1,42 @@
+import * as React from 'react';
+
+import {PostMessageAPIMessage} from '../common/types';
+import {ActionMessageTypes, EventMessageTypes} from '../common/types/actions';
+
+export function sendEventPostMessage(
+ action: K,
+ data: EventMessageTypes[K],
+) {
+ const message = {action, data} as PostMessageAPIMessage;
+ window.parent.postMessage(message);
+}
+
+export function listenPostMessageActions(
+ action: K,
+ callback: (data: ActionMessageTypes[K]) => void,
+) {
+ const onMessage = (e: MessageEvent) => {
+ const message = e.data as PostMessageAPIMessage;
+
+ if ('action' in message && message.action === action) {
+ return callback(message.data);
+ }
+
+ return undefined;
+ };
+
+ window.addEventListener('message', onMessage);
+
+ return () => {
+ window.removeEventListener('message', onMessage);
+ };
+}
+
+export function useInternalPostMessageAPIListener(
+ action: K,
+ callback: (data: ActionMessageTypes[K]) => void,
+) {
+ React.useEffect(() => {
+ return listenPostMessageActions(action, callback);
+ }, [action, callback]);
+}
diff --git a/src/models/constructor-items/blocks.ts b/src/models/constructor-items/blocks.ts
index ceea101f2..6651206a7 100644
--- a/src/models/constructor-items/blocks.ts
+++ b/src/models/constructor-items/blocks.ts
@@ -63,6 +63,7 @@ export enum BlockType {
FilterBlock = 'filter-block',
FormBlock = 'form-block',
// unstable
+ TestEditorBlock = 'test-editor-block',
SliderNewBlock = 'slider-new-block',
}
diff --git a/src/models/constructor.ts b/src/models/constructor.ts
index 786100727..477086ac3 100644
--- a/src/models/constructor.ts
+++ b/src/models/constructor.ts
@@ -1,6 +1,12 @@
import * as React from 'react';
-import {Animatable, BlockDecorationProps, ConstructorItem, ThemedMediaProps} from './';
+import {
+ Animatable,
+ BlockDecorationProps,
+ ConstructorItem,
+ NavigationData,
+ ThemedMediaProps,
+} from './';
export interface PageData {
content: PageContent;
@@ -18,6 +24,10 @@ export interface PageContent extends Animatable {
background?: ThemedMediaProps;
}
+export interface PageContentWithNavigation extends PageContent {
+ navigation?: NavigationData;
+}
+
export interface InitConstrucorState {
hasMenu: boolean;
}
diff --git a/src/models/navigation.ts b/src/models/navigation.ts
index 0f1e8bc8b..977749861 100644
--- a/src/models/navigation.ts
+++ b/src/models/navigation.ts
@@ -120,8 +120,8 @@ export interface FooterData {
}
export interface NavigationData {
- logo: ThemedNavigationLogoData;
- header: HeaderData;
+ logo?: ThemedNavigationLogoData;
+ header?: HeaderData;
footer?: FooterData;
renderNavigation?: () => React.ReactNode;
}
diff --git a/src/navigation/components/DesktopNavigation/DesktopNavigation.tsx b/src/navigation/components/DesktopNavigation/DesktopNavigation.tsx
index 138594aa8..3ee691bc4 100644
--- a/src/navigation/components/DesktopNavigation/DesktopNavigation.tsx
+++ b/src/navigation/components/DesktopNavigation/DesktopNavigation.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import OverflowScroller from '../../../components/OverflowScroller/OverflowScroller';
-import {block} from '../../../utils';
+import {block, isLogoSet} from '../../../utils';
import {DesktopNavigationProps, ItemColumnName, NavigationLayout} from '../../models';
import Logo from '../Logo/Logo';
import {MobileMenuButton} from '../MobileMenuButton/MobileMenuButton';
@@ -22,7 +22,7 @@ export const DesktopNavigation: React.FC = ({
activeItemId,
}) => (
- {logo && (
+ {isLogoSet(logo) && (
diff --git a/src/navigation/components/Navigation/Navigation.tsx b/src/navigation/components/Navigation/Navigation.tsx
index a3161fcef..f5278113f 100644
--- a/src/navigation/components/Navigation/Navigation.tsx
+++ b/src/navigation/components/Navigation/Navigation.tsx
@@ -13,8 +13,8 @@ import './Navigation.scss';
const b = block('navigation');
export interface NavigationComponentProps extends ClassNameProps {
- logo: ThemedNavigationLogoData;
- data: HeaderData;
+ logo?: ThemedNavigationLogoData;
+ data?: HeaderData;
mobilePortalContainer?: React.RefObject
;
}
@@ -25,13 +25,13 @@ export const Navigation: React.FC = ({
mobilePortalContainer,
}) => {
const {
- leftItems,
+ leftItems = [],
rightItems,
customMobileHeaderItems,
iconSize = 20,
withBorder = false,
withBorderOnScroll = true,
- } = data;
+ } = data || {};
const [isSidebarOpened, setIsSidebarOpened] = React.useState(false);
const [showBorder] = useShowBorder(withBorder, withBorderOnScroll);
diff --git a/src/navigation/components/NavigationItem/NavigationItem.tsx b/src/navigation/components/NavigationItem/NavigationItem.tsx
index 161e2c8d5..d6dfec9f6 100644
--- a/src/navigation/components/NavigationItem/NavigationItem.tsx
+++ b/src/navigation/components/NavigationItem/NavigationItem.tsx
@@ -48,7 +48,9 @@ export const NavigationItem: React.FC = ({
}, [data, props, type, menuLayout]);
return (
-
+ // TODO: fix any
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+
diff --git a/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx b/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx
index 36f57fd44..3d891e080 100644
--- a/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx
+++ b/src/navigation/components/NavigationItem/components/NavigationButton/NavigationButton.tsx
@@ -18,7 +18,9 @@ export const NavigationButton: React.FC = (props) => {
const {url, target, className} = props;
const classes = b(null, className);
return (
-
+ // TODO: fix any
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+
{target ? (
) : (
diff --git a/src/navigation/containers/Layout/Layout.scss b/src/navigation/containers/Layout/Layout.scss
index 7d2a45b72..1cc4f8956 100644
--- a/src/navigation/containers/Layout/Layout.scss
+++ b/src/navigation/containers/Layout/Layout.scss
@@ -6,7 +6,8 @@ $block: '.#{$ns}layout';
display: flex;
flex-direction: column;
- min-height: 100vh;
+ // TODO: It seems not okay
+ //min-height: 100vh;
&__content {
display: flex;
diff --git a/src/navigation/containers/Layout/Layout.tsx b/src/navigation/containers/Layout/Layout.tsx
index cc72bdf87..c05e184df 100644
--- a/src/navigation/containers/Layout/Layout.tsx
+++ b/src/navigation/containers/Layout/Layout.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import {NavigationData} from '../../../models';
-import {block} from '../../../utils';
+import {block, isHeaderSet, isLogoSet} from '../../../utils';
import Navigation from '../../components/Navigation/Navigation';
import './Layout.scss';
@@ -16,6 +16,7 @@ export interface LayoutProps {
const Layout: React.FC = ({children, navigation}) => (
{navigation &&
+ (isLogoSet(navigation.logo) || isHeaderSet(navigation.header)) &&
(navigation.renderNavigation ? (
navigation.renderNavigation()
) : (
diff --git a/src/navigation/models.ts b/src/navigation/models.ts
index 010db51be..b6cc530bd 100644
--- a/src/navigation/models.ts
+++ b/src/navigation/models.ts
@@ -63,7 +63,7 @@ export interface ItemsWrapperProps
ClassNameProps {}
export interface DesktopNavigationProps extends MobileMenuButtonProps, ActiveItemProps {
- logo: ThemedNavigationLogoData;
+ logo?: ThemedNavigationLogoData;
leftItemsWithIconSize: NavigationItemModel[];
rightItemsWithIconSize?: NavigationItemModel[];
customMobileHeaderItems?: NavigationItemModel[];
diff --git a/src/navigation/schema.ts b/src/navigation/schema.ts
index 5c809d633..7c2b80f1b 100644
--- a/src/navigation/schema.ts
+++ b/src/navigation/schema.ts
@@ -86,9 +86,18 @@ const NavigationDropdownItemProps = {
const NavigationItemProps = {
oneOf: [
- filteredArray(NavigationLinkItemProps),
- filteredArray(NavigationButtonItemProps),
- filteredArray(NavigationDropdownItemProps),
+ {
+ optionName: 'link',
+ ...filteredArray(NavigationLinkItemProps),
+ },
+ {
+ optionName: 'button',
+ ...filteredArray(NavigationButtonItemProps),
+ },
+ {
+ optionName: 'dropdown',
+ ...filteredArray(NavigationDropdownItemProps),
+ },
],
};
diff --git a/src/schema/constants.ts b/src/schema/constants.ts
index 0ce6a61bd..220e88fe9 100644
--- a/src/schema/constants.ts
+++ b/src/schema/constants.ts
@@ -1,3 +1,4 @@
+import {TestEditorBlockSchema} from '../blocks/TestEditorBlock';
import {BlockType} from '../models';
import {
@@ -57,6 +58,7 @@ export const blockSchemas: Record
= {
...ShareBlock,
...FilterBlock,
...FormBlock,
+ ...TestEditorBlockSchema,
...SliderNewBlock,
};
diff --git a/src/schema/index.ts b/src/schema/index.ts
index c7f90d8b9..42f16a0b7 100644
--- a/src/schema/index.ts
+++ b/src/schema/index.ts
@@ -33,6 +33,17 @@ export const getBlocksCases = (blocks: Schema) => {
);
};
+export const defaultComponentsConfigurationSchema = {
+ type: 'object',
+ properties: {
+ ...AnimatableProps,
+ logo: withTheme(LogoProps),
+ header: NavigationHeaderProps,
+ menu: MenuProps,
+ background: withTheme(BackgroundProps),
+ },
+};
+
export function generateDefaultSchema(config?: SchemaCustomConfig) {
const {cards = {}, blocks = {}, extensions = {}} = config ?? {};
@@ -82,17 +93,13 @@ export function generateDefaultSchema(config?: SchemaCustomConfig) {
additionalProperties: false,
required: ['blocks'],
properties: {
- ...AnimatableProps,
- logo: withTheme(LogoProps),
- header: NavigationHeaderProps,
+ ...defaultComponentsConfigurationSchema.properties,
blocks: {
type: 'array',
items: {
$ref: '#/definitions/children',
},
},
- menu: MenuProps,
- background: withTheme(BackgroundProps),
...extensions,
},
} as Schema;
diff --git a/src/sub-blocks/BackgroundCard/dynamic-form.ts b/src/sub-blocks/BackgroundCard/dynamic-form.ts
new file mode 100644
index 000000000..0fd1bc900
--- /dev/null
+++ b/src/sub-blocks/BackgroundCard/dynamic-form.ts
@@ -0,0 +1,81 @@
+import {BlockConfig} from '../../form-generator';
+import {contentThemes, textSize} from '../../schema/validators/common';
+
+const textSizeEnum = textSize.map((size) => ({value: size, content: size}));
+const contentThemesEnum = contentThemes.map((size) => ({value: size, content: size}));
+
+export const blockConfig: BlockConfig = {
+ name: 'Background Card',
+ inputs: [
+ {
+ type: 'oneOf',
+ name: 'title',
+ title: 'Title Object',
+ options: [
+ {
+ title: 'Simple',
+ value: 'simple',
+ properties: [
+ {
+ type: 'text',
+ name: '',
+ title: 'Title',
+ },
+ ],
+ },
+ {
+ title: 'Complex',
+ value: 'complex',
+ properties: [
+ {
+ type: 'text',
+ name: 'text',
+ title: 'Title',
+ },
+ {
+ type: 'select',
+ name: 'textSize',
+ title: 'Text Size',
+ enum: textSizeEnum,
+ view: 'select',
+ mode: 'single',
+ },
+ {
+ type: 'text',
+ name: 'url',
+ title: 'Url',
+ },
+ {
+ type: 'text',
+ name: 'urlTitle',
+ title: 'Url',
+ },
+ {
+ type: 'boolean',
+ name: 'resetMargin',
+ title: 'Reset Margin',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'textarea',
+ name: 'text',
+ title: 'Description',
+ },
+ {
+ type: 'textarea',
+ name: 'additionalInfo',
+ title: 'Additional Info',
+ },
+ {
+ type: 'select',
+ name: 'size',
+ title: 'Size',
+ enum: contentThemesEnum,
+ view: 'select',
+ mode: 'single',
+ },
+ ],
+};
diff --git a/src/sub-blocks/BackgroundCard/index.tsx b/src/sub-blocks/BackgroundCard/index.tsx
new file mode 100644
index 000000000..0e68204a0
--- /dev/null
+++ b/src/sub-blocks/BackgroundCard/index.tsx
@@ -0,0 +1,26 @@
+import {JSONSchemaType} from 'ajv';
+
+import {BlockData} from '../../constructor-items';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import BackgroundCard from './BackgroundCard';
+import {BackgroundCard as BackgroundCardSchema} from './schema';
+
+const BackgroundCardConfig: BlockData = {
+ component: BackgroundCard,
+ schema: {
+ name: 'Background Card',
+ inputs: generateFromAJV(
+ BackgroundCardSchema['background-card'] as unknown as JSONSchemaType<{}>,
+ ),
+ group: '@gravity-ui/page-constructor/Cards',
+ default: {
+ title: 'Background Card',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ additionalInfo: 'Additional info',
+ backgroundColor: '#F0F0F0',
+ },
+ },
+};
+
+export default BackgroundCardConfig;
diff --git a/src/sub-blocks/BannerCard/BannerCard.tsx b/src/sub-blocks/BannerCard/BannerCard.tsx
index c35f58345..702ac2973 100644
--- a/src/sub-blocks/BannerCard/BannerCard.tsx
+++ b/src/sub-blocks/BannerCard/BannerCard.tsx
@@ -11,7 +11,7 @@ export const BannerCard = (props: BannerCardProps) => {
const {
title,
subtitle,
- button: {url, text, target, theme: buttonTheme = 'raised'} = {},
+ button,
color,
theme: textTheme = 'light',
image,
@@ -21,6 +21,8 @@ export const BannerCard = (props: BannerCardProps) => {
const theme = useTheme();
const contentStyle: Record = {};
+ const {url, text, target, theme: buttonTheme = 'raised'} = button || {};
+
if (color) {
contentStyle.backgroundColor = getThemedValue(color, theme);
}
diff --git a/src/sub-blocks/BannerCard/index.tsx b/src/sub-blocks/BannerCard/index.tsx
new file mode 100644
index 000000000..112be1208
--- /dev/null
+++ b/src/sub-blocks/BannerCard/index.tsx
@@ -0,0 +1,26 @@
+import {JSONSchemaType} from 'ajv';
+
+import {BannerCardProps} from '../../blocks/Banner/schema';
+import {BlockData} from '../../constructor-items';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import BannerCard from './BannerCard';
+
+const BannerCardConfig: BlockData = {
+ component: BannerCard,
+ schema: {
+ name: 'Banner Card',
+ group: '@gravity-ui/page-constructor/Cards',
+ inputs: generateFromAJV(BannerCardProps as unknown as JSONSchemaType<{}>),
+ default: {
+ color: 'rgba(54, 151, 241, 0.4)',
+ title: 'Banner Card',
+ subtitle: 'Some sort of description.',
+ button: {
+ text: 'Read more',
+ },
+ },
+ },
+};
+
+export default BannerCardConfig;
diff --git a/src/sub-blocks/BasicCard/index.tsx b/src/sub-blocks/BasicCard/index.tsx
new file mode 100644
index 000000000..6684f95eb
--- /dev/null
+++ b/src/sub-blocks/BasicCard/index.tsx
@@ -0,0 +1,22 @@
+import {JSONSchemaType} from 'ajv';
+
+import {BlockData} from '../../constructor-items';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import BasicCard from './BasicCard';
+import {BasicCard as BasicCardSchema} from './schema';
+
+const BasicCardConfig: BlockData = {
+ component: BasicCard,
+ schema: {
+ name: 'Basic Card',
+ group: '@gravity-ui/page-constructor/Cards',
+ inputs: generateFromAJV(BasicCardSchema['basic-card'] as unknown as JSONSchemaType<{}>),
+ default: {
+ title: 'Basic Card',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ },
+};
+
+export default BasicCardConfig;
diff --git a/src/sub-blocks/Content/index.tsx b/src/sub-blocks/Content/index.tsx
new file mode 100644
index 000000000..b88dd7c25
--- /dev/null
+++ b/src/sub-blocks/Content/index.tsx
@@ -0,0 +1,22 @@
+import {JSONSchemaType} from 'ajv';
+
+import {BlockData} from '../../constructor-items';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import Content from './Content';
+import {ContentBlock} from './schema';
+
+const ContentConfig: BlockData = {
+ component: Content,
+ schema: {
+ name: 'Content',
+ group: '@gravity-ui/page-constructor/Cards',
+ inputs: generateFromAJV(ContentBlock['content'] as unknown as JSONSchemaType<{}>),
+ default: {
+ title: 'Content',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ },
+};
+
+export default ContentConfig;
diff --git a/src/sub-blocks/Content/schema.ts b/src/sub-blocks/Content/schema.ts
index 84d29852d..0aa9623e9 100644
--- a/src/sub-blocks/Content/schema.ts
+++ b/src/sub-blocks/Content/schema.ts
@@ -69,6 +69,7 @@ export const ContentBase = {
export const ContentBlock = {
content: {
+ type: 'object',
additionalProperties: false,
properties: {
...ContentBase,
diff --git a/src/sub-blocks/Divider/index.tsx b/src/sub-blocks/Divider/index.tsx
new file mode 100644
index 000000000..8256c4fbc
--- /dev/null
+++ b/src/sub-blocks/Divider/index.tsx
@@ -0,0 +1,18 @@
+import {JSONSchemaType} from 'ajv';
+
+import {BlockData} from '../../constructor-items';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import Divider from './Divider';
+import {Divider as DividerSchema} from './schema';
+
+const DividerConfig: BlockData = {
+ component: Divider,
+ schema: {
+ name: 'Divider',
+ inputs: generateFromAJV(DividerSchema['divider'] as unknown as JSONSchemaType<{}>),
+ default: {},
+ },
+};
+
+export default DividerConfig;
diff --git a/src/sub-blocks/ImageCard/ImageCard.tsx b/src/sub-blocks/ImageCard/ImageCard.tsx
index 735105306..0c503362c 100644
--- a/src/sub-blocks/ImageCard/ImageCard.tsx
+++ b/src/sub-blocks/ImageCard/ImageCard.tsx
@@ -47,12 +47,14 @@ const ImageCard = (props: ImageCardProps) => {
const cardContent = (
-
-
-
+ {image && (
+
+
+
+ )}
{hasContent && (
),
+ default: {
+ title: 'Image Card',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ },
+};
+
+export default ImageCardConfig;
diff --git a/src/sub-blocks/LayoutItem/LayoutItem.tsx b/src/sub-blocks/LayoutItem/LayoutItem.tsx
index c2577d354..fdc646636 100644
--- a/src/sub-blocks/LayoutItem/LayoutItem.tsx
+++ b/src/sub-blocks/LayoutItem/LayoutItem.tsx
@@ -16,7 +16,7 @@ import './LayoutItem.scss';
const b = block('layout-item');
const LayoutItem = ({
- content: {links, ...content},
+ content: {links = [], ...content} = {},
contentMargin = 'm',
metaInfo,
media,
diff --git a/src/sub-blocks/LayoutItem/index.tsx b/src/sub-blocks/LayoutItem/index.tsx
new file mode 100644
index 000000000..4492fa3f2
--- /dev/null
+++ b/src/sub-blocks/LayoutItem/index.tsx
@@ -0,0 +1,28 @@
+import {JSONSchemaType} from 'ajv';
+
+import {BlockData} from '../../constructor-items';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import LayoutItem from './LayoutItem';
+import {LayoutItem as LayoutItemSchema} from './schema';
+
+const LayoutItemConfig: BlockData = {
+ component: LayoutItem,
+ schema: {
+ name: 'Layout Item',
+ group: '@gravity-ui/page-constructor/Cards',
+ inputs: generateFromAJV(LayoutItemSchema as unknown as JSONSchemaType<{}>),
+ default: {
+ type: 'layout-item',
+ content: {
+ title: 'Lorem ipsum',
+ text: 'Dolor sit amet',
+ },
+ media: {
+ image: 'https://storage.yandexcloud.net/yc-www-community-images/event_ecaf1ef1-bc3a-40fa-adef-827b0959e6c3.jpg',
+ },
+ },
+ },
+};
+
+export default LayoutItemConfig;
diff --git a/src/sub-blocks/LayoutItem/schema.ts b/src/sub-blocks/LayoutItem/schema.ts
index 4ae79c4f2..9f47d54e1 100644
--- a/src/sub-blocks/LayoutItem/schema.ts
+++ b/src/sub-blocks/LayoutItem/schema.ts
@@ -1,7 +1,8 @@
import omit from 'lodash/omit';
+import {Media} from '../../blocks/Media/schema';
import metaInfo from '../../components/MetaInfo/schema';
-import {BaseProps, CardLayoutProps, MediaProps} from '../../schema/validators/common';
+import {BaseProps, CardLayoutProps} from '../../schema/validators/common';
import {AnalyticsEventSchema} from '../../schema/validators/event';
import {ContentBase} from '../../sub-blocks/Content/schema';
@@ -12,7 +13,7 @@ export const LayoutItem = {
properties: {
...BaseProps,
...CardLayoutProps,
- media: MediaProps,
+ media: Media,
content: omit(ContentBase, ['colSize', 'size', 'centered']),
contentMargin: {
type: 'string',
diff --git a/src/sub-blocks/MediaCard/index.tsx b/src/sub-blocks/MediaCard/index.tsx
new file mode 100644
index 000000000..acbe66859
--- /dev/null
+++ b/src/sub-blocks/MediaCard/index.tsx
@@ -0,0 +1,25 @@
+import {JSONSchemaType} from 'ajv';
+
+import {BlockData} from '../../constructor-items';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import MediaCard from './MediaCard';
+import {MediaCardBlock as MediaCardSchema} from './schema';
+
+const MediaCardConfig: BlockData = {
+ component: MediaCard,
+ schema: {
+ name: 'Media Card',
+ group: '@gravity-ui/page-constructor/Cards',
+ inputs: generateFromAJV(MediaCardSchema['media-card'] as unknown as JSONSchemaType<{}>),
+ default: {
+ content: {
+ title: 'Media Card',
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ image: 'https://storage.yandexcloud.net/yc-www-community-images/event_ecaf1ef1-bc3a-40fa-adef-827b0959e6c3.jpg',
+ },
+ },
+};
+
+export default MediaCardConfig;
diff --git a/src/sub-blocks/PriceCard/index.tsx b/src/sub-blocks/PriceCard/index.tsx
new file mode 100644
index 000000000..6f24d48e4
--- /dev/null
+++ b/src/sub-blocks/PriceCard/index.tsx
@@ -0,0 +1,35 @@
+import {JSONSchemaType} from 'ajv';
+
+import {BlockData} from '../../constructor-items';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import PriceCard from './PriceCard';
+import {PriceCardBlock as PriceCardSchema} from './schema';
+
+const PriceCardConfig: BlockData = {
+ component: PriceCard,
+ schema: {
+ name: 'Price Card',
+ group: '@gravity-ui/page-constructor/Cards',
+ inputs: generateFromAJV(PriceCardSchema['price-card'] as unknown as JSONSchemaType<{}>),
+ default: {
+ type: 'price-card',
+ border: 'line',
+ title: 'Basic',
+ price: '100 $',
+ pricePeriod: 'month',
+ priceDetails: '+ 5% from check',
+ description: 'For any purposes',
+ buttons: [
+ {
+ url: '/',
+ text: 'Read More',
+ width: 'max',
+ theme: 'action',
+ },
+ ],
+ },
+ },
+};
+
+export default PriceCardConfig;
diff --git a/src/sub-blocks/PriceDetailed/PriceDetailed.tsx b/src/sub-blocks/PriceDetailed/PriceDetailed.tsx
index e1fb017cb..e94bb6c7f 100644
--- a/src/sub-blocks/PriceDetailed/PriceDetailed.tsx
+++ b/src/sub-blocks/PriceDetailed/PriceDetailed.tsx
@@ -18,7 +18,7 @@ import SeparatePriceDetailed from './SeparatePriceDetailed/SeparatePriceDetailed
const PriceDetailed = (props: PriceDetailedProps) => {
const {
priceType = PriceDetailsType.SETTINGS,
- items,
+ items = [],
numberGroupItems = 1,
description,
details,
diff --git a/src/sub-blocks/PriceDetailed/index.tsx b/src/sub-blocks/PriceDetailed/index.tsx
new file mode 100644
index 000000000..c2b8827d2
--- /dev/null
+++ b/src/sub-blocks/PriceDetailed/index.tsx
@@ -0,0 +1,39 @@
+import {JSONSchemaType} from 'ajv';
+
+import {BlockData} from '../../constructor-items';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import PriceDetailed from './PriceDetailed';
+import {PriceDetailedBlock as PriceDetailedSchema} from './schema';
+
+/** @deprecated */
+const PriceDetailedConfig: BlockData = {
+ component: PriceDetailed,
+ schema: {
+ name: 'Price Detailed',
+ group: '@gravity-ui/page-constructor/Cards',
+ inputs: generateFromAJV(
+ PriceDetailedSchema['price-detailed'] as unknown as JSONSchemaType<{}>,
+ ),
+ default: {
+ priceType: 'marked-list',
+ items: [
+ {
+ title: '100$',
+ description: 'Basic edition',
+ detailedTitle: 'per year',
+ items: [
+ {
+ text: 'First item',
+ },
+ {
+ text: 'Second item',
+ },
+ ],
+ },
+ ],
+ },
+ },
+};
+
+export default PriceDetailedConfig;
diff --git a/src/sub-blocks/Quote/index.tsx b/src/sub-blocks/Quote/index.tsx
new file mode 100644
index 000000000..13de58b54
--- /dev/null
+++ b/src/sub-blocks/Quote/index.tsx
@@ -0,0 +1,25 @@
+import {JSONSchemaType} from 'ajv';
+
+import {BlockData} from '../../constructor-items';
+import {generateFromAJV} from '../../utils/form-generator';
+
+import Quote from './Quote';
+import {Quote as QuoteSchema} from './schema';
+
+const QuoteConfig: BlockData = {
+ component: Quote,
+ schema: {
+ name: 'Quote',
+ group: '@gravity-ui/page-constructor/Cards',
+ inputs: generateFromAJV(QuoteSchema['quote'] as unknown as JSONSchemaType<{}>),
+ default: {
+ text: 'A good decision is based on knowledge and not on numbers.',
+ author: {
+ firstName: ' Plato',
+ description: 'Greek philosopher',
+ },
+ },
+ },
+};
+
+export default QuoteConfig;
diff --git a/src/utils/editor.ts b/src/utils/editor.ts
new file mode 100644
index 000000000..b0cd861c5
--- /dev/null
+++ b/src/utils/editor.ts
@@ -0,0 +1,23 @@
+import * as React from 'react';
+
+export const getCursorPositionOverElement = (
+ elementRect: DOMRect,
+ mouseEvent: React.MouseEvent,
+) => {
+ const cursorPositionY = elementRect.height - (mouseEvent.clientY - elementRect.y);
+ const cursorPositionX = elementRect.width - (mouseEvent.clientX - elementRect.x);
+ const cursorRatioY = elementRect.height / 2 / cursorPositionY;
+ const cursorRatioX = elementRect.width / 2 / cursorPositionX;
+
+ if (cursorRatioY > cursorRatioX) {
+ if (cursorRatioY >= 1) {
+ return 'bottom';
+ } else {
+ return 'left';
+ }
+ } else if (cursorRatioX >= 1) {
+ return 'right';
+ } else {
+ return 'top';
+ }
+};
diff --git a/src/utils/form-generator.ts b/src/utils/form-generator.ts
new file mode 100644
index 000000000..61e7fe302
--- /dev/null
+++ b/src/utils/form-generator.ts
@@ -0,0 +1,173 @@
+import {JSONSchemaType} from 'ajv';
+
+import {
+ ArrayBaseInput,
+ BooleanInput,
+ ConfigInput,
+ NumberInput,
+ ObjectInput,
+ SelectBaseInput,
+ TextAreaInput,
+ TextInput,
+} from '../form-generator';
+
+export const generateFromAJV = (schema: JSONSchemaType<{}>): ConfigInput[] => {
+ if (schema && schema.properties) {
+ const obj = Object.entries(schema.properties).map(([key, value]) => {
+ const innerSchema = value as JSONSchemaType<{}>;
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ return generateSingleEntity(key, innerSchema);
+ });
+
+ return obj.filter(Boolean) as ConfigInput[];
+ }
+ return [];
+};
+
+export const generateSingleEntity = (key: string, schema: JSONSchemaType<{}>) => {
+ const type = schema.type;
+
+ if (!type && schema.enum) {
+ return {
+ type: 'select',
+ view: 'select',
+ name: key,
+ title: key,
+ enum: schema.enum.map((enumValue: string) => ({
+ content: enumValue,
+ value: enumValue,
+ })),
+ } as SelectBaseInput;
+ }
+
+ if (schema.oneOf) {
+ return {
+ type: 'oneOf',
+ name: key,
+ title: key,
+ options: schema.oneOf.map((item: JSONSchemaType<{}>) => {
+ let properties;
+ if (item.properties) {
+ properties = generateFromAJV(item);
+ } else {
+ properties = [
+ generateSingleEntity('', {
+ ...item,
+ name: '',
+ title: item.optionName,
+ }),
+ ];
+ }
+
+ return {
+ value: item.optionName,
+ title: item.optionName,
+ properties: properties,
+ };
+ }),
+ };
+ }
+
+ if (schema.anyOf) {
+ return {
+ type: 'anyOf',
+ name: key,
+ title: key,
+ options: schema.anyOf.map((item: JSONSchemaType<{}>) => {
+ let properties;
+ if (item.properties) {
+ properties = generateFromAJV(item);
+ } else {
+ properties = [
+ generateSingleEntity('', {
+ ...item,
+ name: '',
+ title: item.optionName,
+ }),
+ ];
+ }
+
+ return {
+ value: item.optionName,
+ title: item.optionName,
+ properties: properties,
+ };
+ }),
+ };
+ }
+
+ switch (type) {
+ case 'string': {
+ if (schema.inputType === 'textarea') {
+ return {
+ type: 'textarea',
+ name: key,
+ title: key,
+ } as TextAreaInput;
+ }
+ if (schema.enum) {
+ return {
+ type: 'select',
+ view: 'select',
+ name: key,
+ title: key,
+ enum: schema.enum.map((enumValue: string) => ({
+ content: enumValue,
+ value: enumValue,
+ })),
+ } as SelectBaseInput;
+ }
+ return {
+ type: 'text',
+ name: key,
+ title: key,
+ } as TextInput;
+ }
+ case 'number': {
+ return {
+ type: 'number',
+ name: key,
+ title: key,
+ } as NumberInput;
+ }
+ case 'object': {
+ return {
+ type: 'object',
+ name: key,
+ title: key,
+ properties: generateFromAJV(schema),
+ } as ObjectInput;
+ }
+ case 'boolean': {
+ return {
+ type: 'boolean',
+ name: key,
+ title: key,
+ properties: generateFromAJV(schema),
+ } as BooleanInput;
+ }
+ case 'array': {
+ if (schema.items.type === 'string') {
+ return {
+ type: 'array',
+ name: key,
+ title: key,
+ properties: generateFromAJV(schema.items),
+ buttonText: 'Add',
+ arrayType: 'text',
+ } as ArrayBaseInput;
+ }
+
+ return {
+ type: 'array',
+ name: key,
+ title: key,
+ properties: generateFromAJV(schema.items),
+ buttonText: 'Add',
+ arrayType: 'object',
+ } as ArrayBaseInput;
+ }
+ }
+
+ return undefined;
+};
diff --git a/src/utils/index.ts b/src/utils/index.ts
index b25a11deb..e188c094a 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -5,6 +5,7 @@ export * from './url';
export * from './cn';
export * from './url';
export * from './theme';
+export * from './navigation';
export type {HubspotEventData, HubspotEventHandlers, HubspotEventName} from './hubspot';
export {isHubspotEventData} from './hubspot';
diff --git a/src/utils/navigation.ts b/src/utils/navigation.ts
new file mode 100644
index 000000000..080f95073
--- /dev/null
+++ b/src/utils/navigation.ts
@@ -0,0 +1,12 @@
+import _get from 'lodash/get';
+import _isEmpty from 'lodash/isEmpty';
+
+import {HeaderData, ThemedNavigationLogoData} from '../models';
+
+export function isLogoSet(logo?: ThemedNavigationLogoData): logo is ThemedNavigationLogoData {
+ return Boolean(_get(logo, 'icon') || _get(logo, 'light.icon'));
+}
+
+export function isHeaderSet(header?: HeaderData): header is HeaderData {
+ return !_isEmpty(header?.leftItems);
+}
diff --git a/styles/mixins.scss b/styles/mixins.scss
index 855eef6c0..43bc986db 100644
--- a/styles/mixins.scss
+++ b/styles/mixins.scss
@@ -177,12 +177,12 @@
@mixin block {
@include add-specificity(&) {
- margin-top: $indentL;
- padding: 0 0 $indentL;
+ //margin-top: $indentL;
+ padding: $indentL 0;
&:first-child {
// @deprecated
- margin-top: var(--pc-first-block-indent, #{$indentXXL});
+ //margin-top: var(--pc-first-block-indent, #{$indentXXL});
}
}
}
@@ -581,27 +581,27 @@ unpredictable css rules order in build */
@include add-specificity(&) {
&_indentTop {
&_0 {
- margin-top: 0;
+ padding-top: 0;
}
&_xs {
- margin-top: $indentXS;
+ padding-top: $indentXS;
}
&_s {
- margin-top: $indentSM;
+ padding-top: $indentSM;
}
&_m {
- margin-top: $indentM;
+ padding-top: $indentM;
}
&_l {
- margin-top: $indentL;
+ padding-top: $indentL;
}
&_xl {
- margin-top: $indentXL;
+ padding-top: $indentXL;
}
}
diff --git a/styles/yfm.scss b/styles/yfm.scss
index ec34d55d1..7c1a30175 100644
--- a/styles/yfm.scss
+++ b/styles/yfm.scss
@@ -1,4 +1,4 @@
-@import '~@diplodoc/transform/dist/css/yfm.css';
+@import '@diplodoc/transform/dist/css/yfm.css';
@import './mixins.scss';
@import './variables.scss';