diff --git a/gulpfile.js b/gulpfile.js index d60aed80..e46b51b1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -27,6 +27,7 @@ function compileTs(modules = false) { '!src/**/__stories__/**/*', '!src/**/__tests__/**/*', '!src/**/__snapshots__/**/*', + '!src/lib/unstable/**/*', ]) .pipe( replace(/import '.+\.scss';/g, (match) => @@ -52,7 +53,12 @@ task('copy-i18n', () => { }); task('styles-components', () => { - return src(['src/**/*.scss', '!src/**/__stories__/**/*', '!src/stories/**/*']) + return src([ + 'src/**/*.scss', + '!src/**/__stories__/**/*', + '!src/stories/**/*', + '!src/lib/unstable/**/*', + ]) .pipe(sass().on('error', sass.logError)) .pipe(dest(path.resolve(BUILD_DIR, 'esm'))) .pipe(dest(path.resolve(BUILD_DIR, 'cjs'))); diff --git a/package-lock.json b/package-lock.json index becc7cde..e6227e69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "@types/react-is": "^17.0.3", "@types/uuid": "^9.0.4", "@vitejs/plugin-react": "^4.2.0", + "ajv": "^8.17.1", "css-loader": "^5.2.6", "eslint": "^8.49.0", "final-form": "^4.20.2", @@ -2382,28 +2383,6 @@ "node": ">=v14" } }, - "node_modules/@commitlint/config-validator/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/@commitlint/ensure": { "version": "17.4.4", "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-17.4.4.tgz", @@ -3530,6 +3509,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/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" + } + }, "node_modules/@eslint/eslintrc/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3563,6 +3558,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -7024,15 +7025,15 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "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" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -7057,30 +7058,6 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -10778,6 +10755,22 @@ "node": ">=10" } }, + "node_modules/eslint/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" + } + }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -10972,6 +10965,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/eslint/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/eslint/node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -16853,9 +16852,9 @@ "dev": true }, "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==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { @@ -20901,6 +20900,28 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/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" + } + }, + "node_modules/schema-utils/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/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -22191,28 +22212,6 @@ "node": ">=10.0.0" } }, - "node_modules/table/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -22276,23 +22275,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -22331,13 +22313,6 @@ "node": ">= 10.13.0" } }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "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", @@ -24039,23 +24014,6 @@ } } }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -24069,13 +24027,6 @@ "ajv": "^8.8.2" } }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/webpack-dev-middleware/node_modules/schema-utils": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", @@ -24125,23 +24076,6 @@ "dev": true, "license": "MIT" }, - "node_modules/webpack/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/webpack/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -24155,13 +24089,6 @@ "ajv": "^8.8.2" } }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/webpack/node_modules/schema-utils": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", @@ -26175,26 +26102,6 @@ "requires": { "@commitlint/types": "^17.4.4", "ajv": "^8.11.0" - }, - "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } } }, "@commitlint/ensure": { @@ -26857,6 +26764,18 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -26881,6 +26800,12 @@ "argparse": "^2.0.1" } }, + "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 + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -29312,15 +29237,15 @@ } }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" } }, "ajv-formats": { @@ -29330,26 +29255,6 @@ "dev": true, "requires": { "ajv": "^8.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } } }, "ajv-keywords": { @@ -31886,6 +31791,18 @@ "text-table": "^0.2.0" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -32017,6 +31934,12 @@ "argparse": "^2.0.1" } }, + "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 + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -36634,9 +36557,9 @@ "dev": true }, "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==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -39625,6 +39548,26 @@ "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "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 + } } }, "semver": { @@ -40612,26 +40555,6 @@ "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } } }, "tapable": { @@ -40673,18 +40596,6 @@ "terser": "^5.31.1" }, "dependencies": { - "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -40711,12 +40622,6 @@ "supports-color": "^8.0.0" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "schema-utils": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", @@ -41833,18 +41738,6 @@ "webpack-sources": "^3.2.3" }, "dependencies": { - "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -41854,12 +41747,6 @@ "fast-deep-equal": "^3.1.3" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "schema-utils": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", @@ -41887,18 +41774,6 @@ "schema-utils": "^4.0.0" }, "dependencies": { - "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -41908,12 +41783,6 @@ "fast-deep-equal": "^3.1.3" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "schema-utils": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", diff --git a/package.json b/package.json index 1d2cc47f..68393252 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "@types/react-is": "^17.0.3", "@types/uuid": "^9.0.4", "@vitejs/plugin-react": "^4.2.0", + "ajv": "^8.17.1", "css-loader": "^5.2.6", "eslint": "^8.49.0", "final-form": "^4.20.2", diff --git a/src/lib/unstable/core/components/Entity/Entity.tsx b/src/lib/unstable/core/components/Entity/Entity.tsx new file mode 100644 index 00000000..15c69f09 --- /dev/null +++ b/src/lib/unstable/core/components/Entity/Entity.tsx @@ -0,0 +1,84 @@ +import React from 'react'; + +import {useField} from 'react-final-form'; + +import type {JsonSchema} from '../../types'; +import {SchemaRendererContext} from '../SchemaRenderer/context'; + +import {getRenderKit, getValidate} from './utils'; + +export interface EntityProps { + name: string; + schema: JsonSchema; +} + +const Component: React.FC = ({name, schema}) => { + const { + config, + errorMessages, + mode, + name: headName, + tools: {setValidationCache, setValidationWaiters}, + } = React.useContext(SchemaRendererContext); + + const head = headName === name; + + const renderKit = React.useMemo( + () => getRenderKit({config, mode, schema}), + [config, mode, schema], + ); + + const options = React.useMemo(() => { + const validate = head + ? getValidate({ + config, + errorMessages, + name, + schema, + setValidationCache, + setValidationWaiters, + }) + : undefined; + + return { + data: {schema}, + defaultValue: schema.default, + subscription: {data: true, error: true, validating: true, value: true}, + validate, + }; + }, [config, errorMessages, head, name, schema, setValidationCache, setValidationWaiters]); + + const field = useField(name, options); + + if (!renderKit.View) { + return null; + } + + let content = null; + + if (renderKit.independent) { + content = ( + + ); + } else { + content = ; + + if (renderKit.Wrapper) { + content = ( + + {content} + + ); + } + } + + return
{content}
; +}; + +export const Entity = React.memo(Component); diff --git a/src/lib/unstable/core/components/Entity/index.ts b/src/lib/unstable/core/components/Entity/index.ts new file mode 100644 index 00000000..4bc5e410 --- /dev/null +++ b/src/lib/unstable/core/components/Entity/index.ts @@ -0,0 +1 @@ +export * from './Entity'; diff --git a/src/lib/unstable/core/components/Entity/types.ts b/src/lib/unstable/core/components/Entity/types.ts new file mode 100644 index 00000000..c7f28350 --- /dev/null +++ b/src/lib/unstable/core/components/Entity/types.ts @@ -0,0 +1,109 @@ +import type {ErrorObject, ValidateFunction} from 'ajv'; +import type {FieldValidator} from 'final-form'; + +import type {SchemaRendererMode} from '../../constants'; +import type { + ErrorMessages, + FieldValue, + IndependentView, + JsonSchema, + SchemaRendererConfig, + SchemaToValueType, + SetValidationCacheMutator, + SetValidationWaitersMutator, + SimpleView, + Validator, + Wrapper, +} from '../../types'; + +/** + * getRenderKit types start + */ + +export type GetRenderKitParams = { + config: SchemaRendererConfig; + mode: SchemaRendererMode; + schema: Schema; +}; + +export type GetRenderKitReturn = + | { + View: SimpleView | undefined; + Wrapper: Wrapper | undefined; + independent: false | undefined; + viewProps: Record; + wrapperProps: Record; + } + | { + View: IndependentView; + Wrapper: Wrapper | undefined; + independent: true; + viewProps: Record; + wrapperProps: Record; + }; + +/** + * getRenderKit types end + */ + +/** + * getAjvValidate types start + */ + +export type EntityParametersError = ErrorObject< + 'entityParameters', + { + schema: JsonSchema; + validator: Validator; + value: FieldValue | null | undefined; + } +>; + +export type GetAjvValidateParams = { + config: SchemaRendererConfig; + schema: JsonSchema; +}; + +export interface GetAjvValidateReturn extends ValidateFunction { + errors?: (ErrorObject | EntityParametersError)[]; +} + +/** + * getAjvValidate types end + */ + +/** + * getError types start + */ + +export type GetErrorParams = { + errorMessages: Record; + instancePath: string; + keyword: string; + schema: JsonSchema; + schemaPath: string; +}; + +/** + * getError types end + */ +/** + * getValidate types start + */ + +export type GetValidateParams = { + config: SchemaRendererConfig; + errorMessages: ErrorMessages; + name: string; + schema: Schema; + setValidationCache: SetValidationCacheMutator; + setValidationWaiters: SetValidationWaitersMutator; +}; + +export type GetValidateReturn = FieldValidator< + SchemaToValueType +>; + +/** + * getValidate types end + */ diff --git a/src/lib/unstable/core/components/Entity/utils.ts b/src/lib/unstable/core/components/Entity/utils.ts new file mode 100644 index 00000000..7a0adce4 --- /dev/null +++ b/src/lib/unstable/core/components/Entity/utils.ts @@ -0,0 +1,305 @@ +import type {ErrorObject, FuncKeywordDefinition, SchemaValidateFunction} from 'ajv'; +import Ajv from 'ajv'; +import {ARRAY_ERROR} from 'final-form'; +import get from 'lodash/get'; +import isEqual from 'lodash/isEqual'; +import isString from 'lodash/isString'; +import omit from 'lodash/omit'; +import set from 'lodash/set'; + +import {EMPTY_OBJECT, JsonSchemaType} from '../../constants'; +import type { + FieldValue, + JsonSchema, + ObjectValue, + SyncValidateError, + ValidationState, + ValidationWaiter, + Validator, + View, + Wrapper, +} from '../../types'; + +import type { + EntityParametersError, + GetAjvValidateParams, + GetAjvValidateReturn, + GetRenderKitParams, + GetRenderKitReturn, + GetValidateParams, + GetValidateReturn, +} from './types'; + +export const getRenderKit = ({ + config, + mode, + schema, +}: GetRenderKitParams): GetRenderKitReturn => { + const viewType: string | undefined = get(schema, 'entityParameters.viewType'); + const wrapperType: string | undefined = get(schema, 'entityParameters.wrapperType'); + + const viewProps = get(schema, 'entityParameters.viewProps', EMPTY_OBJECT); + const wrapperProps = get(schema, 'entityParameters.wrapperProps', EMPTY_OBJECT); + + const ViewComponent: View | undefined = get( + config, + `${schema.type}.views.${viewType}.${mode}.Component`, + ); + const WrapperComponent: Wrapper | undefined = get( + config, + `${schema.type}.wrappers.${wrapperType}`, + ); + const independent: boolean | undefined = get( + config, + `${schema.type}.views.${viewType}.${mode}.independent`, + ); + + return { + View: ViewComponent, + viewProps, + Wrapper: WrapperComponent, + wrapperProps, + independent, + }; +}; + +export const getAjvValidate = ({ + config, + schema: mainSchema, +}: GetAjvValidateParams): GetAjvValidateReturn => { + function entityParametersValidate(_: any, value: FieldValue, schema?: JsonSchema) { + if (schema) { + const validatorType: string | undefined = get(schema, 'entityParameters.validatorType'); + const validator: Validator | undefined = get( + config, + `${schema.type}.validators.${validatorType}`, + ); + + if (validator) { + const error: Partial = { + keyword: 'entityParameters', + message: '', + params: {validator, value, schema}, + }; + + (entityParametersValidate as SchemaValidateFunction).errors = [error]; + + return false; + } + } + + return true; + } + + const ajv = new Ajv({ + allErrors: true, + allowMatchingProperties: true, + keywords: [ + { + errors: true, + keyword: 'entityParameters', + validate: entityParametersValidate as FuncKeywordDefinition['validate'], + }, + ], + }); + const ajvValidate = ajv.compile(mainSchema) as GetAjvValidateReturn; + + return ajvValidate; +}; + +const getSchemaBySchemaPath = ( + schemaPath: string, + keyword: string, + mainSchema: JsonSchema, +): JsonSchema | undefined => { + const sp = decodeURIComponent(schemaPath); + let result = mainSchema; + + if (sp !== `#/${keyword}`) { + const pathArr = sp.substring('#/'.length, sp.length - `/${keyword}`.length).split('/'); + + result = get(mainSchema, pathArr); + } + + return result; +}; + +const getSchemaByInstancePath = ( + instancePath: string, + mainSchema: JsonSchema, +): JsonSchema | undefined => { + let result: JsonSchema | undefined = mainSchema; + + if (instancePath.length) { + const pathArr = instancePath.substring('/'.length).split('/'); + + result = pathArr.reduce((acc: JsonSchema | undefined, subpath) => { + const type = get(acc, 'type'); + + if (type === JsonSchemaType.Object) { + return get(acc, `properties.${subpath}`); + } else if (type === JsonSchemaType.Array) { + const items = get(acc, 'items'); + + if (Array.isArray(items)) { + return get(items, `[${subpath}]`); + } + + return items; + } + + return undefined; + }, mainSchema); + } + + return result; +}; + +const isEntityParametersError = ( + error: ErrorObject | EntityParametersError, +): error is EntityParametersError => error.keyword === 'entityParameters'; + +const getErrorMessageBySchema = (schema: JsonSchema | undefined, keyword: string, name: string) => { + const errorOrMap: Record | string | undefined = get( + schema, + `entityParameters.errorMessages.${keyword}`, + ); + const message: string | undefined = isString(errorOrMap) ? errorOrMap : get(errorOrMap, name); + + return message; +}; + +const getError = (schema: JsonSchema, message: SyncValidateError) => { + if (schema.type === JsonSchemaType.Array) { + const arrayError = set([], ARRAY_ERROR, message); + + return arrayError; + } + + if (schema.type === JsonSchemaType.Object) { + const objectError = set({}, 'FINAL_FORM/object-error', message); + + return objectError; + } + + return message; +}; + +export const getValidate = ({ + config, + errorMessages, + name, + schema, + setValidationCache, + setValidationWaiters, +}: GetValidateParams): GetValidateReturn => { + const ajvValidate = getAjvValidate({config, schema}); + + return (value, allValues, meta) => { + ajvValidate(value); + + if (!ajvValidate.errors?.length) { + return false; + } + + const waiters: Record = {}; + const errors: {instancePath: string; error: SyncValidateError}[] = []; + const priorityErrors: {instancePath: string; error: SyncValidateError}[] = []; + + ajvValidate.errors.forEach((ajvError) => { + if (isEntityParametersError(ajvError)) { + const {instancePath, params} = ajvError; + const validationState: ValidationState | undefined = meta?.data; + + const waiter = validationState?.waiters?.[instancePath]; + const cache = validationState?.cache?.[instancePath]; + const cacheItem = cache?.find((item) => isEqual(params, omit(item, 'result'))); + + if (cacheItem?.result) { + priorityErrors.push({ + instancePath: instancePath, + error: cacheItem.result, + }); + } else if (!waiter || !isEqual(params, waiter)) { + const errorOrPromise = params.validator(params.value, allValues as ObjectValue); + + if (errorOrPromise instanceof Promise) { + waiters[instancePath] = params; + + errorOrPromise.then((result) => { + setValidationCache({ + name, + cache: {[ajvError.instancePath]: {...ajvError.params, result}}, + }); + }); + } else { + priorityErrors.push({ + instancePath: instancePath, + error: errorOrPromise, + }); + } + } + } else { + let instancePath = ajvError.instancePath; + let keyword = ajvError.keyword; + let schemaPath = ajvError.schemaPath; + + if (keyword === 'required' || keyword === 'dependencies') { + instancePath += `/${ajvError.params.missingProperty}`; + } else if (keyword === 'if') { + keyword = ajvError.params.failingKeyword; + schemaPath = schemaPath.slice(0, -'if'.length) + ajvError.params.failingKeyword; + } + + const spSchema: JsonSchema | undefined = getSchemaBySchemaPath( + schemaPath, + keyword, + schema, + ); + const ipSchema: JsonSchema | undefined = getSchemaByInstancePath( + instancePath, + schema, + ); + + const propertyName = instancePath.split('/').pop() as string; + const error: string | undefined = + getErrorMessageBySchema(spSchema, keyword, propertyName) || + getErrorMessageBySchema(ipSchema, keyword, propertyName) || + errorMessages[keyword as keyof typeof errorMessages] || + ajvError.message; + + errors.push({ + instancePath, + error, + }); + } + }); + + if (Object.keys(waiters).length) { + setValidationWaiters({name, waiters: waiters}); + } + + const error = set({}, name, undefined); + + [...errors, ...priorityErrors].forEach((item) => { + if (item.error) { + const path = [ + ...(name.length ? name.split('.') : []), + ...(item.instancePath + ? item.instancePath.substring('/'.length).split('/') + : []), + ]; + + const itemSchema = getSchemaByInstancePath(item.instancePath, schema); + + if (itemSchema) { + set(error, path, getError(itemSchema, item.error)); + } + } + }); + + // сетить ошибки массивов и объектов в отдельное место в сторе формы + + return get(error, name); + }; +}; diff --git a/src/lib/unstable/core/components/SchemaRenderer/SchemaRenderer.tsx b/src/lib/unstable/core/components/SchemaRenderer/SchemaRenderer.tsx new file mode 100644 index 00000000..351df782 --- /dev/null +++ b/src/lib/unstable/core/components/SchemaRenderer/SchemaRenderer.tsx @@ -0,0 +1,53 @@ +import React from 'react'; + +import {useForm} from 'react-final-form'; + +import type {SchemaRendererMode} from '../../constants'; +import {EMPTY_OBJECT} from '../../constants'; +import type { + ErrorMessages, + JsonSchema, + SchemaRendererConfig, + SchemaRendererContextType, +} from '../../types'; +import {Entity} from '../Entity'; + +import {SchemaRendererContext} from './context'; + +export interface SchemaRendererProps { + config: SchemaRendererConfig; + errorMessages?: ErrorMessages; + mode: SchemaRendererMode; + name: string; + schema: JsonSchema; +} + +const Component: React.FC = ({ + config, + errorMessages = EMPTY_OBJECT, + mode, + name, + schema, +}) => { + const {setValidationCache, setValidationWaiters} = useForm().mutators; + + const context: SchemaRendererContextType = React.useMemo( + () => ({ + config, + errorMessages, + mode, + name, + schema, + tools: {setValidationCache, setValidationWaiters}, + }), + [config, errorMessages, mode, name, schema, setValidationCache, setValidationWaiters], + ); + + return ( + + + + ); +}; + +export const SchemaRenderer = React.memo(Component); diff --git a/src/lib/unstable/core/components/SchemaRenderer/context.ts b/src/lib/unstable/core/components/SchemaRenderer/context.ts new file mode 100644 index 00000000..81fa4c33 --- /dev/null +++ b/src/lib/unstable/core/components/SchemaRenderer/context.ts @@ -0,0 +1,7 @@ +import React from 'react'; + +import type {SchemaRendererContextType} from '../../types'; + +export const SchemaRendererContext = React.createContext( + null as unknown as SchemaRendererContextType, +); diff --git a/src/lib/unstable/core/components/SchemaRenderer/index.ts b/src/lib/unstable/core/components/SchemaRenderer/index.ts new file mode 100644 index 00000000..58f7aa13 --- /dev/null +++ b/src/lib/unstable/core/components/SchemaRenderer/index.ts @@ -0,0 +1,2 @@ +export * from './SchemaRenderer'; +export * from './context'; diff --git a/src/lib/unstable/core/constants/common.ts b/src/lib/unstable/core/constants/common.ts new file mode 100644 index 00000000..d5ec5282 --- /dev/null +++ b/src/lib/unstable/core/constants/common.ts @@ -0,0 +1,14 @@ +export enum SchemaRendererMode { + Form = 'form', + Overview = 'overview', +} + +export enum JsonSchemaType { + Array = 'array', + Boolean = 'boolean', + Number = 'number', + Object = 'object', + String = 'string', +} + +export const EMPTY_OBJECT = {}; diff --git a/src/lib/unstable/core/constants/index.ts b/src/lib/unstable/core/constants/index.ts new file mode 100644 index 00000000..d0b93236 --- /dev/null +++ b/src/lib/unstable/core/constants/index.ts @@ -0,0 +1 @@ +export * from './common'; diff --git a/src/lib/unstable/core/mutators/async-validation/async-validation.ts b/src/lib/unstable/core/mutators/async-validation/async-validation.ts new file mode 100644 index 00000000..fd0bb288 --- /dev/null +++ b/src/lib/unstable/core/mutators/async-validation/async-validation.ts @@ -0,0 +1,51 @@ +import isEqual from 'lodash/isEqual'; +import omit from 'lodash/omit'; + +import type { + SetValidationCacheFunction, + SetValidationWaitersFunction, + ValidationState, +} from '../../types'; + +export const setValidationWaiters: SetValidationWaitersFunction = ( + [{name, waiters}], + mutableState, +) => { + const validationState = mutableState.fields[name]?.data as ValidationState | undefined; + + if (validationState && waiters) { + Object.keys(waiters).forEach((waiterName) => { + validationState.waiters = { + ...validationState.waiters, + [waiterName]: waiters[waiterName], + }; + + const waiterField = mutableState.fields[waiterName]; + + if (waiterField) { + waiterField.validating = true; + } + }); + } +}; + +export const setValidationCache: SetValidationCacheFunction = ([{cache, name}], mutableState) => { + const validationState = mutableState.fields[name]?.data as ValidationState | undefined; + + if (validationState && cache) { + Object.keys(cache).forEach((cacheName) => { + validationState.cache = { + ...validationState.cache, + [cacheName]: [...(validationState.cache?.[cacheName] || []), cache[cacheName]], + }; + + const cacheField = mutableState.fields[cacheName]; + const waiter = validationState.waiters?.[cacheName]; + + if (cacheField && waiter && isEqual(waiter, omit(cache[cacheName], 'result'))) { + validationState.waiters = omit(validationState.waiters, cacheName); + cacheField.validating = false; + } + }); + } +}; diff --git a/src/lib/unstable/core/mutators/async-validation/index.ts b/src/lib/unstable/core/mutators/async-validation/index.ts new file mode 100644 index 00000000..0dfe05bd --- /dev/null +++ b/src/lib/unstable/core/mutators/async-validation/index.ts @@ -0,0 +1 @@ +export * from './async-validation'; diff --git a/src/lib/unstable/core/mutators/index.ts b/src/lib/unstable/core/mutators/index.ts new file mode 100644 index 00000000..88115419 --- /dev/null +++ b/src/lib/unstable/core/mutators/index.ts @@ -0,0 +1,3 @@ +import {setValidationCache, setValidationWaiters} from './async-validation'; + +export const mutators = {setValidationCache, setValidationWaiters}; diff --git a/src/lib/unstable/core/types/components.ts b/src/lib/unstable/core/types/components.ts new file mode 100644 index 00000000..04b13d6b --- /dev/null +++ b/src/lib/unstable/core/types/components.ts @@ -0,0 +1,47 @@ +import type {FieldRenderProps} from 'react-final-form'; + +import type {SchemaToValueType} from './helpers'; +import type {JsonSchema} from './schema'; + +export interface SimpleViewProps< + Schema extends JsonSchema, + ViewComponentProps extends Record = {}, +> extends FieldRenderProps | null | undefined> { + schema: Schema; + viewProps: Partial; +} + +export type SimpleView< + Schema extends JsonSchema, + ViewComponentProps extends Record = {}, +> = React.ComponentType>; + +export interface WrapperProps< + Schema extends JsonSchema, + WrapperComponentProps extends Record = {}, +> extends FieldRenderProps | null | undefined> { + schema: Schema; + wrapperProps: Partial; +} + +export type Wrapper< + Schema extends JsonSchema, + WrapperComponentProps extends Record = {}, +> = React.ComponentType>; + +export interface IndependentViewProps< + Schema extends JsonSchema, + ComponentProps extends Record = {}, + WrapperComponentProps extends Record = {}, +> extends SimpleViewProps { + Wrapper?: Wrapper; + wrapperProps: Partial; +} + +export type IndependentView< + Schema extends JsonSchema, + ComponentProps extends Record = {}, + WrapperComponentProps extends Record = {}, +> = React.ComponentType>; + +export type View = SimpleView | IndependentView; diff --git a/src/lib/unstable/core/types/config.ts b/src/lib/unstable/core/types/config.ts new file mode 100644 index 00000000..236fa348 --- /dev/null +++ b/src/lib/unstable/core/types/config.ts @@ -0,0 +1,57 @@ +import type {JsonSchemaType, SchemaRendererMode} from '../constants'; + +import type {IndependentView, SimpleView, Wrapper} from './components'; +import type { + JsonSchema, + JsonSchemaArray, + JsonSchemaBoolean, + JsonSchemaNumber, + JsonSchemaObject, + JsonSchemaString, +} from './schema'; +import type {Validator} from './validation'; + +export interface SimpleViewComponentConfig { + Component: SimpleView | null; + independent?: false; +} + +export interface IndependentViewComponentConfig { + Component: IndependentView; + independent: true; +} + +export type ViewComponentConfig = + | SimpleViewComponentConfig + | IndependentViewComponentConfig; + +export interface ViewConfig { + [SchemaRendererMode.Form]: ViewComponentConfig; + [SchemaRendererMode.Overview]: ViewComponentConfig; +} + +export interface ViewsConfig { + [key: string]: ViewConfig | undefined; +} + +export interface WrappersConfig { + [key: string]: Wrapper | undefined; +} + +export interface ValidatorsConfig { + [key: string]: Validator | undefined; +} + +export interface TypeConfig { + views: ViewsConfig; + wrappers: WrappersConfig; + validators: ValidatorsConfig; +} + +export interface SchemaRendererConfig { + [JsonSchemaType.Array]: TypeConfig; + [JsonSchemaType.Boolean]: TypeConfig; + [JsonSchemaType.Number]: TypeConfig; + [JsonSchemaType.Object]: TypeConfig; + [JsonSchemaType.String]: TypeConfig; +} diff --git a/src/lib/unstable/core/types/context.ts b/src/lib/unstable/core/types/context.ts new file mode 100644 index 00000000..e775d14d --- /dev/null +++ b/src/lib/unstable/core/types/context.ts @@ -0,0 +1,18 @@ +import type {SchemaRendererMode} from '../constants'; + +import type {SchemaRendererConfig} from './config'; +import type {SetValidationCacheMutator, SetValidationWaitersMutator} from './mutators'; +import type {JsonSchema} from './schema'; +import type {ErrorMessages} from './validation'; + +export interface SchemaRendererContextType { + config: SchemaRendererConfig; + errorMessages: ErrorMessages; + mode: SchemaRendererMode; + name: string; + schema: JsonSchema; + tools: { + setValidationCache: SetValidationCacheMutator; + setValidationWaiters: SetValidationWaitersMutator; + }; +} diff --git a/src/lib/unstable/core/types/helpers.ts b/src/lib/unstable/core/types/helpers.ts new file mode 100644 index 00000000..be7de6b5 --- /dev/null +++ b/src/lib/unstable/core/types/helpers.ts @@ -0,0 +1,83 @@ +import type {JsonSchemaType, SchemaRendererMode} from '../constants'; + +import type {SchemaRendererConfig} from './config'; +import type { + JsonSchema, + JsonSchemaArray, + JsonSchemaBoolean, + JsonSchemaNumber, + JsonSchemaObject, + JsonSchemaString, +} from './schema'; +import type {ArrayValue, FieldValue, ObjectValue} from './values'; + +export type SchemaToValueType = Schema extends JsonSchemaArray + ? ArrayValue + : Schema extends JsonSchemaBoolean + ? boolean + : Schema extends JsonSchemaNumber + ? number + : Schema extends JsonSchemaObject + ? ObjectValue + : Schema extends JsonSchemaString + ? string + : FieldValue; + +export type SchemaToSchemaType = Schema extends JsonSchemaArray + ? JsonSchemaType.Array + : Schema extends JsonSchemaBoolean + ? JsonSchemaType.Boolean + : Schema extends JsonSchemaNumber + ? JsonSchemaType.Number + : Schema extends JsonSchemaObject + ? JsonSchemaType.Object + : Schema extends JsonSchemaString + ? JsonSchemaType.String + : JsonSchemaType; + +export type SchemaTypeToSchema = + SchemaType extends JsonSchemaType.Array + ? JsonSchemaArray + : SchemaType extends JsonSchemaType.Boolean + ? JsonSchemaBoolean + : SchemaType extends JsonSchemaType.Number + ? JsonSchemaNumber + : SchemaType extends JsonSchemaType.Object + ? JsonSchemaObject + : SchemaType extends JsonSchemaType.String + ? JsonSchemaString + : JsonSchema; + +type ExtractViewProps = ViewConfig extends { + [SchemaRendererMode.Form]: infer FormComponentConfig; + [SchemaRendererMode.Overview]: infer OverviewComponentConfig; +} + ? (FormComponentConfig extends { + Component: React.ComponentType; + } + ? FormViewComponentProps extends {viewProps: infer ViewComponentProps} + ? ViewComponentProps + : {} + : {}) & + (OverviewComponentConfig extends { + Component: React.ComponentType; + } + ? OverivewViewComponentProps extends {viewProps: infer ViewComponentProps} + ? ViewComponentProps + : {} + : {}) + : {}; + +export type ViewComponentPropsByConfig = { + [Key in keyof Config['views']]: ExtractViewProps; +}; + +type ExtractWrapperProps = Wrapper extends React.ComponentType + ? WrapperProps extends {wrapperProps: infer WrapperComponentProps} + ? WrapperComponentProps + : {} + : {}; + +export type WrapperComponentPropsByConfig = { + [Key in keyof Config['wrappers']]: ExtractWrapperProps; +}; diff --git a/src/lib/unstable/core/types/index.ts b/src/lib/unstable/core/types/index.ts new file mode 100644 index 00000000..c81222d6 --- /dev/null +++ b/src/lib/unstable/core/types/index.ts @@ -0,0 +1,8 @@ +export * from './components'; +export * from './config'; +export * from './context'; +export * from './helpers'; +export * from './mutators'; +export * from './schema'; +export * from './validation'; +export * from './values'; diff --git a/src/lib/unstable/core/types/mutators.ts b/src/lib/unstable/core/types/mutators.ts new file mode 100644 index 00000000..dbbdd366 --- /dev/null +++ b/src/lib/unstable/core/types/mutators.ts @@ -0,0 +1,68 @@ +import type {MutableState, Tools} from 'final-form'; + +import type {JsonSchema} from './schema'; +import type {SyncValidateError, Validator} from './validation'; +import type {FieldValue} from './values'; + +/** + * async-validation start + */ + +export interface ValidationWaiter { + schema: JsonSchema; + validator: Validator; + value: FieldValue | null | undefined; +} + +export interface ValidationCache extends ValidationWaiter { + result: SyncValidateError; +} + +export interface ValidationState { + cache?: { + [key: string]: ValidationCache[] | undefined; + }; + waiters?: { + [key: string]: ValidationWaiter | undefined; + }; +} + +export interface SetValidationWaitersParams { + name: string; + waiters: { + [key: string]: ValidationWaiter; + }; +} + +export type SetValidationWaitersFunction< + FormValues = object, + InitialFormValues = Partial, +> = ( + args: [SetValidationWaitersParams], + state: MutableState, + tools: Tools, +) => void; + +export type SetValidationWaitersMutator = (params: SetValidationWaitersParams) => void; + +export interface SetValidationCacheParams { + cache: { + [key: string]: ValidationCache; + }; + name: string; +} + +export type SetValidationCacheFunction< + FormValues = object, + InitialFormValues = Partial, +> = ( + args: [SetValidationCacheParams], + state: MutableState, + tools: Tools, +) => void; + +export type SetValidationCacheMutator = (params: SetValidationCacheParams) => void; + +/** + * async-validation end + */ diff --git a/src/lib/unstable/core/types/schema.ts b/src/lib/unstable/core/types/schema.ts new file mode 100644 index 00000000..7afcfed7 --- /dev/null +++ b/src/lib/unstable/core/types/schema.ts @@ -0,0 +1,120 @@ +import type {JsonSchemaType} from '../constants'; + +import type {SchemaRendererConfig} from './config'; +import type {ViewComponentPropsByConfig, WrapperComponentPropsByConfig} from './helpers'; +import type {ErrorMessages} from './validation'; +import type {ArrayValue, FieldValue, ObjectValue} from './values'; + +interface EntityParameters< + Config extends SchemaRendererConfig[JsonSchemaType] = SchemaRendererConfig[JsonSchemaType], + ViewKey extends Exclude, number> = Exclude< + keyof ViewComponentPropsByConfig, + number + >, + WrapperKey extends Exclude, number> = Exclude< + keyof WrapperComponentPropsByConfig, + number + >, +> { + entityParameters?: { + enumDescription?: { + [key: string]: string; + }; + errorMessages?: ErrorMessages & { + dependencies?: Record; + required?: Record; + }; // todo + validatorType?: string; + viewType?: ViewKey; + viewProps?: ViewComponentPropsByConfig[ViewKey]; + wrapperType?: WrapperKey; + wrapperProps?: { + open?: boolean; + hidden?: boolean; + copy?: boolean; + required?: boolean; + } & WrapperComponentPropsByConfig[WrapperKey]; + }; +} + +interface JsonSchemaBase { + allOf?: Schema[]; + anyOf?: Schema[]; + const?: ValueType; + default?: ValueType; + description?: string; + else?: Schema; + enum?: ValueType[]; + examples?: ValueType[]; + if?: Schema; + not?: Schema; + oneOf?: Schema[]; + readOnly?: boolean; + then?: Schema; + title?: string; + writeOnly?: boolean; +} + +export interface JsonSchemaArray + extends JsonSchemaBase, + EntityParameters { + contains?: boolean | JsonSchema; + items?: JsonSchema | JsonSchema[]; + maxItems?: number; + minItems?: number; + type: JsonSchemaType.Array; + uniqueItems?: boolean; +} + +export interface JsonSchemaBoolean + extends JsonSchemaBase, + EntityParameters { + type: JsonSchemaType.Boolean; +} + +export interface JsonSchemaNumber + extends JsonSchemaBase, + EntityParameters { + exclusiveMaximum?: number; + exclusiveMinimum?: number; + maximum?: number; + minimum?: number; + multipleOf?: number; + type: JsonSchemaType.Number; +} + +export interface JsonSchemaObject + extends JsonSchemaBase, + EntityParameters { + additionalProperties?: boolean | JsonSchema; + dependencies?: { + [key: string]: string[] | JsonSchemaObject; + }; + maxProperties?: number; + minProperties?: number; + patternProperties?: { + [key: string]: JsonSchema; + }; + properties?: { + [key: string]: JsonSchema; + }; + propertyNames?: JsonSchema; + required?: string[]; + type: JsonSchemaType.Object; +} + +export interface JsonSchemaString + extends JsonSchemaBase, + EntityParameters { + maxLength?: number; + minLength?: number; + pattern?: string; + type: JsonSchemaType.String; +} + +export type JsonSchema = + | JsonSchemaArray + | JsonSchemaBoolean + | JsonSchemaNumber + | JsonSchemaObject + | JsonSchemaString; diff --git a/src/lib/unstable/core/types/validation.ts b/src/lib/unstable/core/types/validation.ts new file mode 100644 index 00000000..55b0d0d2 --- /dev/null +++ b/src/lib/unstable/core/types/validation.ts @@ -0,0 +1,54 @@ +import type {SchemaToValueType} from './helpers'; +import type {JsonSchema} from './schema'; +import type {ObjectValue} from './values'; + +export type SyncValidateError = + | boolean + | string + | Record + | undefined; + +export type AsyncValidateError = Promise; + +export type SyncValidator = ( + value: SchemaToValueType | null | undefined, + allValues: ObjectValue, +) => SyncValidateError; + +export type AsyncValidator = ( + value: SchemaToValueType | null | undefined, + allValues: ObjectValue, +) => AsyncValidateError; + +export type Validator = SyncValidator | AsyncValidator; + +export interface ErrorMessages { + additionalProperties?: string; + anyOf?: string; + const?: string; + contains?: string; + dependencies?: string; + else?: string; + enum?: string; + exclusiveMaximum?: string; + exclusiveMinimum?: string; + maxContains?: string; + maxItems?: string; + maxLength?: string; + maxProperties?: string; + maximum?: string; + minContains?: string; + minItems?: string; + minLength?: string; + minProperties?: string; + minimum?: string; + multipleOf?: string; + not?: string; + oneOf?: string; + pattern?: string; + propertyNames?: string; + required?: string; + then?: string; + type?: string; + uniqueItems?: string; +} diff --git a/src/lib/unstable/core/types/values.ts b/src/lib/unstable/core/types/values.ts new file mode 100644 index 00000000..5f257190 --- /dev/null +++ b/src/lib/unstable/core/types/values.ts @@ -0,0 +1,3 @@ +export type ArrayValue = FieldValue[]; +export interface ObjectValue extends Record {} +export type FieldValue = number | boolean | string | ArrayValue | ObjectValue; diff --git a/src/lib/unstable/kit/Accordeon.tsx b/src/lib/unstable/kit/Accordeon.tsx new file mode 100644 index 00000000..8ab211fa --- /dev/null +++ b/src/lib/unstable/kit/Accordeon.tsx @@ -0,0 +1,54 @@ +import React from 'react'; + +// import {ArrayLayoutProps, ObjectLayoutProps, isArrayItem} from '../../../../core'; +import {ErrorWrapper, type ErrorWrapperProps} from '../../kit/components'; +// import {useErrorChecker} from '../../../hooks'; +// import {RemoveButton} from '../../kit/components/RemoveButton'; +import {SimpleVerticalAccordeon} from '../../kit/components/SimpleVerticalAccordeon'; +import type {JsonSchema, WrapperProps} from '../core/types'; + +export const Accordeon = ({ + schema, + input, + meta, + children, +}: WrapperProps): JSX.Element => { + const [open, setOpen] = React.useState(Boolean(schema.entityParameters?.wrapperProps?.open)); + + // const onDrop = React.useCallback(() => { + // setOpen(false); + // input.onDrop(); + // }, [input.onDrop, setOpen]); + + // const removeButton = React.useMemo(() => { + // if (!isArrayItem(name) && (spec.required || !input.value)) { + // return null; + // } + + // return ; + // }, [spec.required, input.value, onDrop, name]); + + // useErrorChecker({name, meta, open, setOpen}); + + return ( + + + {children} + + + ); +}; diff --git a/src/lib/unstable/kit/ArrayBase.scss b/src/lib/unstable/kit/ArrayBase.scss new file mode 100644 index 00000000..3e871751 --- /dev/null +++ b/src/lib/unstable/kit/ArrayBase.scss @@ -0,0 +1,35 @@ +.array-base { + &_add-button-right { + display: flex; + align-items: flex-end; + + .transparent { + align-items: flex-end; + } + } + + &__items-wrapper { + &_add-button-down { + margin-bottom: 15px; + } + + &_items-primitive { + min-width: 100%; + + &:empty { + min-width: unset; + } + } + } + + &__item-prefix { + margin-top: -7px; + margin-bottom: 8px; + } + + &__add-button { + &_right { + margin-left: 4px; + } + } +} diff --git a/src/lib/unstable/kit/ArrayBase.tsx b/src/lib/unstable/kit/ArrayBase.tsx new file mode 100644 index 00000000..6da78be8 --- /dev/null +++ b/src/lib/unstable/kit/ArrayBase.tsx @@ -0,0 +1,154 @@ +import React from 'react'; + +import {Plus} from '@gravity-ui/icons'; +import {Button, Icon} from '@gravity-ui/uikit'; + +// import { +// ArrayInput, +// ArrayValue, +// Controller, +// FieldArrayValue, +// FieldValue, +// OBJECT_ARRAY_CNT, +// OBJECT_ARRAY_FLAG, +// Spec, +// ValidateError, +// isBooleanSpec, +// isCorrectSpec, +// isNumberSpec, +// isStringSpec, +// transformArrIn, +// } from '../../../../core'; +import {block} from '../../kit/utils'; +import {Entity} from '../core/components/Entity'; +import type {JsonSchemaArray, SimpleView} from '../core/types'; + +import './ArrayBase.scss'; + +const b = block('array-base'); + +export const ArrayBase: SimpleView = ({input, schema}) => { + // const keys = React.useMemo( + // () => + // Object.keys(arrayInput.value || {}) + // .filter((k) => k !== OBJECT_ARRAY_FLAG && k !== OBJECT_ARRAY_CNT) + // .map((k) => k.split('<').join('').split('>').join('')) + // .sort((a, b) => Number(a) - Number(b)), + // [arrayInput.value], + // ); + + // const itemSpecCorrect = React.useMemo(() => isCorrectSpec(spec.items), [spec.items]); + + // const itemsPrimitive = React.useMemo(() => { + // return isBooleanSpec(spec.items) || isNumberSpec(spec.items) || isStringSpec(spec.items); + // }, [spec.items]); + + const getItemSpec = React.useCallback( + (idx: number): typeof schema.items | null => { + const itemSpec = {...schema.items}; + + // @ts-expect-error + itemSpec.title = itemSpec.title ? `${itemSpec.title} ${idx + 1}` : `${idx + 1}`; + // @ts-expect-error + return itemSpec; + }, + [schema.items], + ); + + // const parentOnChange = React.useCallback( + // (childName: string, childValue: FieldValue, childErrors?: Record) => + // input.onChange( + // (currentValue) => + // set({...currentValue}, childName.split(`${input.name}.`).join(''), childValue), + // childErrors, + // ), + // [input.onChange, input.name], + // ); + + const AddButton: React.FC = React.useCallback(() => { + let onClick = () => input.onChange([...(input.value || []), undefined]); + + let qa = `${input.name}-add-item`; + let title = schema.title; + + if (!input.value && schema.default) { + onClick = () => { + // input.onChange(transformArrIn(spec.defaultValue!)); + input.onChange(schema.default); + }; + + qa = `${name}-init-arr`; + title = schema.title; + } + + return ( + + ); + }, [ + // arrayInput, + // input, + // name, + // spec.defaultValue, + // spec.viewSpec.disabled, + // spec.viewSpec.itemLabel, + // spec.viewSpec.layoutTitle, + // spec.viewSpec.addButtonPosition, + input, + schema, + ]); + + const items = React.useMemo( + () => + (input.value || []).map((_key, idx) => { + const itemSpec = getItemSpec(idx); + + if (!itemSpec) { + return null; + } + + // const showItemPrefix = idx !== 0 && spec.viewSpec.itemPrefix; + + return ( + + {/* {showItemPrefix ? ( + + ) : null} */} + `]} + // parentOnChange={parentOnChange} + // parentOnUnmount={input.parentOnUnmount} + // spec={itemSpec} + // name={`${name}.<${key}>`} + /> + + ); + }), + [input, schema], + ); + + // if (!itemSpecCorrect) { + // return null; + // } + + return ( +
+
{items}
+ +
+ ); +}; diff --git a/src/lib/unstable/kit/MultiSelect.scss b/src/lib/unstable/kit/MultiSelect.scss new file mode 100644 index 00000000..3bf905a7 --- /dev/null +++ b/src/lib/unstable/kit/MultiSelect.scss @@ -0,0 +1,9 @@ +// @import '../../../styles/variables'; + +.multi-select { + max-width: 305px; + + &__meta-text { + display: block; + } +} diff --git a/src/lib/unstable/kit/MultiSelect.tsx b/src/lib/unstable/kit/MultiSelect.tsx new file mode 100644 index 00000000..ab421e3a --- /dev/null +++ b/src/lib/unstable/kit/MultiSelect.tsx @@ -0,0 +1,109 @@ +import React from 'react'; + +import type {SelectProps as SelectBaseProps} from '@gravity-ui/uikit'; +import {Select} from '@gravity-ui/uikit'; + +// import {ArrayInput, FieldArrayValue, transformArrIn, transformArrOut} from '../../../../core'; +import {block} from '../../kit/utils'; +import type {JsonSchemaArray, SimpleView} from '../core/types'; + +import './MultiSelect.scss'; + +export interface MultiSelectProps + extends Omit< + SelectBaseProps, + | 'onUpdate' + | 'value' + | 'onOpenChange' + | 'disabled' + | 'placeholder' + | 'filterPlaceholder' + | 'multiple' + | 'qa' + > { + withCustomOptions?: boolean; +} + +const b = block('multi-select'); + +export const MultiSelect: SimpleView = ({input, schema}) => { + const {name, value, onBlur, onChange, onFocus} = input; + + const filterable = React.useMemo(() => (schema.enum?.length || 0) > 9, [schema.enum?.length]); + + // const {withCustomOptions, options: externalOptions} = inputProps || {}; + + const options = React.useMemo( + () => + // withCustomOptions + // ? externalOptions || [] + // : + // @ts-expect-error + schema.items?.enum?.map((id) => ({ + id, + value: id, + text: schema.entityParameters?.enumDescription?.[id] || id, + content: schema.entityParameters?.enumDescription?.[id] || id, + key: id, + })), + [ + schema.enum, + schema.description, + // spec.viewSpec.selectParams?.meta, + // externalOptions, + // withCustomOptions, + ], + ); + + const renderOption = React.useCallback((option: {value: string; content?: React.ReactNode}) => { + return {option.content || option.value}; + }, []); + + const getOptionHeight = React.useCallback(() => { + // if (spec.viewSpec.selectParams?.meta) { + // return 44; + // } + + return 28; + }, []); + + const handleToggle = React.useCallback( + (open: boolean) => { + if (open) { + onFocus(); + } else { + onBlur(); + } + }, + [onFocus, onBlur], + ); + + // const _value = React.useMemo(() => transformArrOut(value), [value]); + + // const handleChange = React.useCallback( + // (value: string[]) => onChange(transformArrIn(value)), + // [onChange], + // ); + + return ( +