diff --git a/.swcrc b/.swcrc index 9420c60c..6991ad0a 100644 --- a/.swcrc +++ b/.swcrc @@ -13,11 +13,6 @@ "loose": false, "externalHelpers": true }, - "env": { - "targets": "> 0.5%, last 2 versions, Firefox ESR, not dead, safari >= 15.4, iOS >= 15.4", - "mode": "usage", - "coreJs": "3" - }, "minify": false, "isModule": true } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4aebbfe7..a390a8ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "dependencies": { "@emotion/cache": "^11.14.0", "@swc/helpers": "^0.5.21", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.5", "clsx": "^2.1.1", "lodash.assignwith": "^4.2.0", "lodash.clonedeep": "^4.5.0", @@ -33,8 +35,14 @@ "@mui/icons-material": "^9.0.0", "@mui/material": "^9.0.0", "@rollup/plugin-commonjs": "^29.0.2", + "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-replace": "^6.0.3", "@swc/core": "^1.11.11", + "@types/lodash.assignwith": "^4.2.9", + "@types/lodash.clonedeep": "^4.5.9", + "@types/lodash.find": "^4.6.9", + "@types/lodash.isundefined": "^3.0.9", + "@types/lodash.merge": "^4.6.9", "@vitest/coverage-v8": "^4.1.8", "ajv": "^8.20.0", "chai": "^6.2.2", @@ -67,8 +75,11 @@ "rollup-plugin-swc3": "^0.12.1", "simulant": "^0.2.2", "sinon": "^21.1.2", + "source-map-loader": "^5.0.0", "style-loader": "^4.0.0", "swc-loader": "^0.2.6", + "ts-loader": "^9.6.2", + "typescript": "^7.0.2", "unplugin-swc": "^1.5.9", "vitest": "^4.1.8", "webpack": "^5.106.2", @@ -1135,9 +1146,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1155,9 +1163,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1175,9 +1180,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1195,9 +1197,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1215,9 +1214,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1235,9 +1231,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1255,9 +1248,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1275,9 +1265,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1295,9 +1282,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1321,9 +1305,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1347,9 +1328,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1373,9 +1351,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1399,9 +1374,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1425,9 +1397,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1451,9 +1420,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1477,9 +1443,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2427,9 +2390,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2447,9 +2407,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2467,9 +2424,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2487,9 +2441,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3045,6 +2996,31 @@ } } }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/plugin-replace": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz", @@ -3937,6 +3913,63 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz", + "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash.assignwith": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@types/lodash.assignwith/-/lodash.assignwith-4.2.9.tgz", + "integrity": "sha512-VbhcGOLjk/XPlF74Yfa1AUC2TrR8xYur28V8FWLTWRkAsxqXeyiJcTG7dCzZAjHXAUM3gRT8opYX0/wFlL0+ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.clonedeep": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz", + "integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.find": { + "version": "4.6.9", + "resolved": "https://registry.npmjs.org/@types/lodash.find/-/lodash.find-4.6.9.tgz", + "integrity": "sha512-v1qcNZs0ksl0+BO+aflReb8oIDHLc+rSc4n+RYZ3pMaxIFw2/mcW/wlTYFqrh+8dY1ZqRbX22ytFfUgs4sAXtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.isundefined": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/lodash.isundefined/-/lodash.isundefined-3.0.9.tgz", + "integrity": "sha512-nF+msH20xJzIJyZMBfArDnYxibF/Kt80y9naFYULzZ2yc4eBDAyxVDaf8DXyDWefHDh9CAaUoFp6XhMn38a26g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.merge": { + "version": "4.6.9", + "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.9.tgz", + "integrity": "sha512-23sHDPmzd59kUgWyKGiOMO2Qb9YtqRO/x4IhkgNUiPQ1+5MUVqi6bCZeq9nBJ17msjIMbEIO5u+XW4Kz6aGUhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -3982,6 +4015,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.12", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", @@ -3992,6 +4043,13 @@ "@types/react": "*" } }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", @@ -4062,6 +4120,346 @@ "@types/node": "*" } }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/@vitest/coverage-v8": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", @@ -4529,6 +4927,22 @@ "node": ">=8" } }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -5205,6 +5619,23 @@ "node": ">=18" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/cheerio": { "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", @@ -5347,6 +5778,26 @@ "node": ">=6" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -5763,6 +6214,16 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/default-browser": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", @@ -8239,6 +8700,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, "node_modules/is-negative-zero": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", @@ -11968,6 +12436,40 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -12554,6 +13056,41 @@ "tslib": "2" } }, + "node_modules/ts-loader": { + "version": "9.6.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.6.2.tgz", + "integrity": "sha512-R4iuczmtgxvtuI556s+hTZ6/7Ee03VCAk/l/M8LY1OAsUgB7YydsCxkgq9D9pKRaD7GJqUi2u8fp9zZP/ufjKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "picomatch": "^4.0.0", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "loader-utils": "*", + "typescript": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "loader-utils": { + "optional": true + } + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -12747,6 +13284,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", diff --git a/package.json b/package.json index 5c9422de..472fc976 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "8.0.0", "description": "Datatables for React using Material-UI", "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist" ], @@ -16,9 +17,9 @@ "docs:start": "next start docs", "coverage": "cross-env NODE_ENV=test vitest run --coverage --coverage.reporter=text", "coverage:html": "cross-env NODE_ENV=test vitest run --coverage", - "prettier": "find src/ docs/ test/ -type f -name \"*.js\" ! -path \"*/.next/*\" | xargs prettier --write", + "prettier": "find src/ -type f \\( -name \"*.ts\" -o -name \"*.tsx\" \\) | xargs prettier --write && find docs/ test/ -type f -name \"*.js\" ! -path \"*/.next/*\" | xargs prettier --write", "lint": "eslint src", - "build": "cross-env NODE_ENV=production npm run prettier && rollup -c", + "build": "cross-env NODE_ENV=production npm run prettier && rollup -c && tsc --emitDeclarationOnly", "prepare": "npm run build" }, "repository": { @@ -44,15 +45,21 @@ "@mui/icons-material": "^9.0.0", "@mui/material": "^9.0.0", "@rollup/plugin-commonjs": "^29.0.2", + "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-replace": "^6.0.3", "@swc/core": "^1.11.11", + "@types/lodash.assignwith": "^4.2.9", + "@types/lodash.clonedeep": "^4.5.9", + "@types/lodash.find": "^4.6.9", + "@types/lodash.isundefined": "^3.0.9", + "@types/lodash.merge": "^4.6.9", "@vitest/coverage-v8": "^4.1.8", + "ajv": "^8.20.0", "chai": "^6.2.2", "core-js": "^3.49.0", "cross-env": "^10.1.0", "css-loader": "^7.1.4", "enzyme": "^3.11.0", - "ajv": "^8.20.0", "eslint": "^10.3.0", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.2", @@ -78,8 +85,11 @@ "rollup-plugin-swc3": "^0.12.1", "simulant": "^0.2.2", "sinon": "^21.1.2", + "source-map-loader": "^5.0.0", "style-loader": "^4.0.0", "swc-loader": "^0.2.6", + "ts-loader": "^9.6.2", + "typescript": "^7.0.2", "unplugin-swc": "^1.5.9", "vitest": "^4.1.8", "webpack": "^5.106.2", @@ -96,6 +106,8 @@ "dependencies": { "@emotion/cache": "^11.14.0", "@swc/helpers": "^0.5.21", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.5", "clsx": "^2.1.1", "lodash.assignwith": "^4.2.0", "lodash.clonedeep": "^4.5.0", diff --git a/prettier.config.js b/prettier.config.js index 195dea07..c850d48f 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -4,6 +4,5 @@ module.exports = { trailingComma: 'all', bracketSpacing: true, bracketSameLine: true, - parser: 'babel', semi: true, }; diff --git a/rollup.config.mjs b/rollup.config.mjs index 0bc02170..1844e07e 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,5 +1,6 @@ import { swc } from 'rollup-plugin-swc3'; import commonjs from '@rollup/plugin-commonjs'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; import replace from '@rollup/plugin-replace'; import uglify from '@lopatnov/rollup-plugin-uglify'; import packageJson from './package.json' with { type: 'json' }; @@ -15,9 +16,12 @@ const externalPackages = [ const isExternal = (id) => externalPackages.some((pkg) => id === pkg || id.startsWith(`${pkg}/`)); export default { - input: 'src/index.js', + input: 'src/index.tsx', external: isExternal, plugins: [ + nodeResolve({ + extensions: ['.tsx', '.ts', '.jsx', '.js'], + }), replace({ 'process.env.NODE_ENV': JSON.stringify('production'), preventAssignment: true, @@ -26,13 +30,13 @@ export default { include: ['node_modules/**'], }), swc({ - include: /\.(js|jsx)$/, + include: /\.(ts|tsx)$/, exclude: /node_modules/, tsconfig: false, jsc: { parser: { - syntax: 'ecmascript', - jsx: true, + syntax: 'typescript', + tsx: true, }, transform: { react: { diff --git a/src/MUIDataTable.js b/src/MUIDataTable.tsx similarity index 81% rename from src/MUIDataTable.js rename to src/MUIDataTable.tsx index 3ab427be..d7d326da 100644 --- a/src/MUIDataTable.js +++ b/src/MUIDataTable.tsx @@ -6,7 +6,6 @@ import cloneDeep from 'lodash.clonedeep'; import find from 'lodash.find'; import isUndefined from 'lodash.isundefined'; import merge from 'lodash.merge'; -import PropTypes from 'prop-types'; import React from 'react'; import { flushSync } from 'react-dom'; import DefaultTableBody from './components/TableBody'; @@ -22,8 +21,46 @@ import { buildMap, getCollatorComparator, getPageValue, sortCompare, warnDepreca import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { load, save } from './localStorage'; +import type { ReactNode, ComponentType } from 'react'; +import type { Theme } from '@mui/material/styles'; +import type { MUIDataTableData } from './types/data'; +import type { MUIDataTableOptions } from './types'; + +export interface MUIDataTableProps { + title: ReactNode; + data: MUIDataTableData; + columns: (string | Record)[]; + options?: MUIDataTableOptions; + className?: string; + components?: Record; + classes: Record; +} + +interface MUIDataTableInternalState { + activeColumn: number | null; + announceText: string | null; + count: number; + columns: Record[]; + expandedRows: { data: { index: number; dataIndex: number }[]; lookup: Record }; + data: { index: number; data: unknown[] }[]; + displayData: { data: unknown[]; dataIndex: number }[]; + filterData: string[][]; + filterList: string[][]; + page: number; + previousSelectedRow: { index: number; dataIndex: number } | null; + rowsPerPage: number; + searchProps: Record; + searchText: string | null; + selectedRows: { data: { index: number; dataIndex: number }[]; lookup: Record }; + showResponsive: boolean; + sortOrder: Record; + columnOrder: number[]; + curSelectedRows?: { index: number; dataIndex: number }[]; + curExpandedRows?: { index: number; dataIndex: number }[]; + [key: string]: unknown; +} -const defaultTableStyles = (theme) => ({ +const defaultTableStyles = (theme: Theme) => ({ root: { '& .datatables-noprint': { '@media print': { @@ -95,7 +132,7 @@ const TABLE_LOAD = { // Populate this list with anything that might render in the toolbar to determine if we hide the toolbar const TOOLBAR_ITEMS = ['title', 'filter', 'search', 'print', 'download', 'viewColumns', 'customToolbar']; -const hasToolbarItem = (options, title) => { +const hasToolbarItem = (options: any, title: any) => { options.title = title; return !isUndefined(find(TOOLBAR_ITEMS, (i) => options[i])); @@ -109,152 +146,14 @@ const STP = { ALWAYS: 'always', }; -class MUIDataTable extends React.Component { - static propTypes = { - /** Title of the table */ - title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, - /** Data used to describe table */ - data: PropTypes.array.isRequired, - /** Columns used to describe table */ - columns: PropTypes.PropTypes.arrayOf( - PropTypes.oneOfType([ - PropTypes.string, - PropTypes.shape({ - label: PropTypes.string, - name: PropTypes.string.isRequired, - options: PropTypes.shape({ - display: PropTypes.oneOf(['true', 'false', 'excluded', 'always', true, false]), - empty: PropTypes.bool, - filter: PropTypes.bool, - sort: PropTypes.bool, - print: PropTypes.bool, - searchable: PropTypes.bool, - download: PropTypes.bool, - viewColumns: PropTypes.bool, - filterList: PropTypes.array, - filterOptions: PropTypes.oneOfType([ - PropTypes.array, - PropTypes.shape({ - names: PropTypes.array, - logic: PropTypes.func, - display: PropTypes.func, - }), - ]), - filterType: PropTypes.oneOf(['dropdown', 'checkbox', 'multiselect', 'textField', 'custom']), - customHeadRender: PropTypes.func, - customBodyRender: PropTypes.func, - customBodyRenderLite: PropTypes.func, - customHeadLabelRender: PropTypes.func, - customFilterListOptions: PropTypes.oneOfType([ - PropTypes.shape({ - render: PropTypes.func, - update: PropTypes.func, - }), - ]), - customFilterListRender: PropTypes.func, - setCellProps: PropTypes.func, - setCellHeaderProps: PropTypes.func, - sortThirdClickReset: PropTypes.bool, - sortDescFirst: PropTypes.bool, - }), - }), - ]), - ).isRequired, - /** Options used to describe table */ - options: PropTypes.shape({ - caseSensitive: PropTypes.bool, - columnOrder: PropTypes.array, - count: PropTypes.number, - confirmFilters: PropTypes.bool, - consoleWarnings: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), - customFilterDialogFooter: PropTypes.func, - customFooter: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), - customRowRender: PropTypes.func, - customSearch: PropTypes.func, - customSearchRender: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), - customSort: PropTypes.func, - customToolbar: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), - customToolbarSelect: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), - draggableColumns: PropTypes.object, - enableNestedDataAccess: PropTypes.string, - expandableRows: PropTypes.bool, - expandableRowsHeader: PropTypes.bool, - expandableRowsOnClick: PropTypes.bool, - disableToolbarSelect: PropTypes.bool, - download: PropTypes.oneOf([true, false, 'true', 'false', 'disabled']), - downloadOptions: PropTypes.shape({ - filename: PropTypes.string, - separator: PropTypes.string, - filterOptions: PropTypes.shape({ - useDisplayedColumnsOnly: PropTypes.bool, - useDisplayedRowsOnly: PropTypes.bool, - }), - }), - filter: PropTypes.oneOf([true, false, 'true', 'false', 'disabled']), - filterArrayFullMatch: PropTypes.bool, - filterType: PropTypes.oneOf(['dropdown', 'checkbox', 'multiselect', 'textField', 'custom']), - fixedHeader: PropTypes.bool, - fixedSelectColumn: PropTypes.bool, - getTextLabels: PropTypes.func, - isRowExpandable: PropTypes.func, - isRowSelectable: PropTypes.func, - jumpToPage: PropTypes.bool, - onDownload: PropTypes.func, - onFilterChange: PropTypes.func, - onFilterChipClose: PropTypes.func, - onFilterConfirm: PropTypes.func, - onFilterDialogOpen: PropTypes.func, - onFilterDialogClose: PropTypes.func, - onRowClick: PropTypes.func, - onRowsExpand: PropTypes.func, - onRowExpansionChange: PropTypes.func, - onRowsSelect: PropTypes.func, - onRowSelectionChange: PropTypes.func, - onTableChange: PropTypes.func, - onTableInit: PropTypes.func, - page: PropTypes.number, - pagination: PropTypes.bool, - print: PropTypes.oneOf([true, false, 'true', 'false', 'disabled']), - searchProps: PropTypes.object, - selectableRows: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['none', 'single', 'multiple'])]), - selectableRowsHeader: PropTypes.bool, - selectableRowsHideCheckboxes: PropTypes.bool, - selectableRowsOnClick: PropTypes.bool, - serverSide: PropTypes.bool, - tableId: PropTypes.string, - tableBodyHeight: PropTypes.string, - tableBodyMaxHeight: PropTypes.string, - renderExpandableRow: PropTypes.func, - resizableColumns: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), - responsive: PropTypes.oneOf(['standard', 'vertical', 'verticalAlways', 'simple']), - rowHover: PropTypes.bool, - rowsExpanded: PropTypes.array, - rowsPerPage: PropTypes.number, - rowsPerPageOptions: PropTypes.array, - rowsSelected: PropTypes.array, - search: PropTypes.oneOf([true, false, 'true', 'false', 'disabled']), - searchOpen: PropTypes.bool, - searchAlwaysOpen: PropTypes.bool, - searchPlaceholder: PropTypes.string, - searchText: PropTypes.string, - setFilterChipProps: PropTypes.func, - setRowProps: PropTypes.func, - selectToolbarPlacement: PropTypes.oneOfType([ - PropTypes.bool, - PropTypes.oneOf([STP.REPLACE, STP.ABOVE, STP.NONE, STP.ALWAYS]), - ]), - setTableProps: PropTypes.func, - sort: PropTypes.bool, - sortOrder: PropTypes.object, - storageKey: PropTypes.string, - viewColumns: PropTypes.oneOf([true, false, 'true', 'false', 'disabled']), - }), - /** Pass and use className to style MUIDataTable as desired */ - className: PropTypes.string, - components: PropTypes.objectOf(PropTypes.any), - }; - - static defaultProps = { +class MUIDataTable extends React.Component { + static defaultProps: { + title: ReactNode; + options: Record; + data: unknown[][]; + columns: (string | Record)[]; + components: Record; + } = { title: '', options: {}, data: [], @@ -273,7 +172,19 @@ class MUIDataTable extends React.Component { }, }; - constructor(props) { + options: any = {}; + tableRef: React.RefObject = React.createRef(); + tableContent: React.RefObject = React.createRef(); + draggableHeadCellRefs: Record = {}; + resizeHeadCellRefs: Record = {}; + timers: Record = {}; + setHeadResizeable: ( + cellsRef: Record, + tableRef: React.RefObject, + ) => void = () => {}; + updateDividers: () => void = () => {}; + + constructor(props: MUIDataTableProps) { super(props); this.tableRef = React.createRef(); this.tableContent = React.createRef(); @@ -283,11 +194,12 @@ class MUIDataTable extends React.Component { this.setHeadResizeable = () => {}; this.updateDividers = () => {}; - let defaultState = { + let defaultState: any = { activeColumn: null, announceText: null, count: 0, columns: [], + columnOrder: [], expandedRows: { data: [], lookup: {}, @@ -311,24 +223,24 @@ class MUIDataTable extends React.Component { this.mergeDefaultOptions(props); - const restoredState = load(props.options.storageKey); + const restoredState = load((props.options as any)?.storageKey); this.state = Object.assign(defaultState, restoredState ? restoredState : this.getInitTableOptions()); this.setTableData = this.setTableData.bind(this); - this.setTableData(props, TABLE_LOAD.INITIAL, true, null, true); + this.setTableData(props, TABLE_LOAD.INITIAL, true, undefined, true); } - componentDidMount() { + override componentDidMount() { this.setHeadResizeable(this.resizeHeadCellRefs, this.tableRef); // When we have a search, we must reset page to view it unless on serverSide since paging is handled by the user. - if (this.props.options.searchText && !this.props.options.serverSide) this.setState({ page: 0 }); + if ((this.props.options as any)?.searchText && !(this.props.options as any)?.serverSide) this.setState({ page: 0 }); this.setTableInit('tableInitialized'); } - componentDidUpdate(prevProps) { + override componentDidUpdate(prevProps: MUIDataTableProps) { if ( this.props.data !== prevProps.data || this.props.columns !== prevProps.columns || @@ -346,7 +258,10 @@ class MUIDataTable extends React.Component { }); } - if (this.props.options.searchText !== prevProps.options.searchText && !this.props.options.serverSide) { + if ( + (this.props.options as any)?.searchText !== (prevProps.options as any)?.searchText && + !(this.props.options as any)?.serverSide + ) { // When we have a search, we must reset page to view it unless on serverSide since paging is handled by the user. this.setState({ page: 0 }); } @@ -360,7 +275,7 @@ class MUIDataTable extends React.Component { } } - updateOptions(options, props) { + updateOptions(options: any, props: any) { // set backwards compatibility options if (props.options.disableToolbarSelect === true && props.options.selectToolbarPlacement === undefined) { // if deprecated option disableToolbarSelect is set and selectToolbarPlacement is default then use it @@ -429,15 +344,15 @@ class MUIDataTable extends React.Component { selectToolbarPlacement: STP.REPLACE, }); - warnDep = (msg, consoleWarnings) => { + warnDep = (msg: string, _consoleWarnings?: any) => { warnDeprecated(msg, this.options.consoleWarnings); }; - warnInfo = (msg, consoleWarnings) => { + warnInfoMsg = (msg: string, _consoleWarnings?: any) => { warnInfo(msg, this.options.consoleWarnings); }; - handleOptionDeprecation = (props) => { + handleOptionDeprecation = (props: any) => { if (typeof this.options.selectableRows === 'boolean') { this.warnDep( 'Using a boolean for selectableRows has been deprecated. Please use string option: multiple | single | none', @@ -460,7 +375,7 @@ class MUIDataTable extends React.Component { ' has been deprecated, but will still work in version 3.x. Please use string option: standard | vertical | simple. More info: https://github.com/gregnb/mui-datatables/tree/master/docs/v2_to_v3_guide.md', ); } else { - this.warnInfo( + this.warnInfoMsg( this.options.responsive + ' is not recognized as a valid input for responsive option. Please use string option: standard | vertical | simple. More info: https://github.com/gregnb/mui-datatables/tree/master/docs/v2_to_v3_guide.md', ); @@ -499,7 +414,7 @@ class MUIDataTable extends React.Component { ); } - props.columns.map((c) => { + props.columns.map((c: any) => { if (c.options && c.options.customFilterListRender) { this.warnDep( 'The customFilterListRender option has been deprecated. It is being replaced by customFilterListOptions.render (Specify customFilterListOptions: { render: Function } in column options.)', @@ -526,7 +441,7 @@ class MUIDataTable extends React.Component { /* * React currently does not support deep merge for defaultProps. Objects are overwritten */ - mergeDefaultOptions(props) { + mergeDefaultOptions(props: any) { const defaultOptions = this.getDefaultOptions(); const theProps = Object.assign({}, props); theProps.options = theProps.options || {}; @@ -534,7 +449,7 @@ class MUIDataTable extends React.Component { this.updateOptions(defaultOptions, theProps); } - validateOptions(options) { + validateOptions(options: any) { if (options.serverSide && options.onTableChange === undefined) { throw Error('onTableChange callback must be provided when using serverSide option'); } @@ -546,16 +461,16 @@ class MUIDataTable extends React.Component { } } - setTableAction = (action) => { + setTableAction = (action: string) => { if (typeof this.options.onTableChange === 'function') { - this.options.onTableChange(action, this.state); + this.options.onTableChange(action, this.state as any); } if (this.options.storageKey) { - save(this.options.storageKey, this.state); + save(this.options.storageKey, this.state as any); } }; - setTableInit = (action) => { + setTableInit = (action: string) => { if (typeof this.options.onTableInit === 'function') { this.options.onTableInit(action, this.state); } @@ -563,7 +478,7 @@ class MUIDataTable extends React.Component { getInitTableOptions() { const optionNames = ['rowsPerPage', 'page', 'rowsSelected', 'rowsPerPageOptions']; - const optState = optionNames.reduce((acc, cur) => { + const optState = optionNames.reduce((acc: any, cur) => { if (this.options[cur] !== undefined) { acc[cur] = this.options[cur]; } @@ -574,7 +489,7 @@ class MUIDataTable extends React.Component { return optState; } - setHeadCellRef = (index, pos, el) => { + setHeadCellRef = (index: number, pos: number, el: HTMLElement | null) => { this.draggableHeadCellRefs[index] = el; this.resizeHeadCellRefs[pos] = el; }; @@ -592,11 +507,11 @@ class MUIDataTable extends React.Component { * prevColumnOrder - columnOrder object saved onto the state. */ - buildColumns = (newColumns, prevColumns = [], newColumnOrder, prevColumnOrder = []) => { - let columnData = []; - let filterData = []; - let filterList = []; - let columnOrder = []; + buildColumns = (newColumns: any[], prevColumns: any[] = [], newColumnOrder: any, prevColumnOrder: any[] = []) => { + let columnData: any[] = []; + let filterData: any[] = []; + let filterList: any[] = []; + let columnOrder: any[] = []; newColumns.forEach((column, colIndex) => { let columnOptions = { @@ -674,10 +589,10 @@ class MUIDataTable extends React.Component { return { columns: columnData, filterData, filterList, columnOrder }; }; - transformData = (columns, data) => { + transformData = (columns: any[], data: any[]) => { const { enableNestedDataAccess } = this.options; - const leaf = (obj, path) => - (enableNestedDataAccess ? path.split(enableNestedDataAccess) : path.split()).reduce( + const leaf = (obj: any, path: string) => + (enableNestedDataAccess ? path.split(enableNestedDataAccess as string) : [path]).reduce( (value, el) => (value ? value[el] : undefined), obj, ); @@ -696,8 +611,14 @@ class MUIDataTable extends React.Component { return transformedData; }; - setTableData(props, status, dataUpdated, callback = () => {}, fromConstructor = false) { - let tableData = []; + setTableData( + props: any, + status: number, + dataUpdated: boolean, + callback: (() => void) | undefined = () => {}, + fromConstructor = false, + ) { + let tableData: any[] = []; let { columns, filterData, filterList, columnOrder } = this.buildColumns( props.columns, this.state.columns, @@ -709,7 +630,7 @@ class MUIDataTable extends React.Component { let sortDirection = 'none'; let tableMeta; - let sortOrder; + let sortOrder: any; if (this.options.sortOrder && this.options.sortOrder.direction && this.options.sortOrder.name) { sortOrder = Object.assign({}, this.options.sortOrder); } else { @@ -717,7 +638,7 @@ class MUIDataTable extends React.Component { // if no sortOrder, check and see if there's a sortDirection on one of the columns (deprecation notice for this is given above) if (!sortOrder.direction) { - props.columns.forEach((column, colIndex) => { + props.columns.forEach((column: any, colIndex: number) => { if (column.options && (column.options.sortDirection === 'asc' || column.options.sortDirection === 'desc')) { sortOrder.name = column.name; sortOrder.sortDirection = column.sortDirection; @@ -760,8 +681,8 @@ class MUIDataTable extends React.Component { tableMeta = this.getTableMeta(rowIndex, colIndex, rowData, column, data, this.state, tableData); const funcResult = column.customBodyRender(value, tableMeta); - if (React.isValidElement(funcResult) && funcResult.props.value) { - value = funcResult.props.value; + if (React.isValidElement(funcResult) && (funcResult.props as any).value) { + value = (funcResult.props as any).value; } else if (typeof funcResult === 'string') { value = funcResult; } @@ -824,12 +745,12 @@ class MUIDataTable extends React.Component { } }); - let selectedRowsData = { + let selectedRowsData: any = { data: [], lookup: {}, }; - let expandedRowsData = { + let expandedRowsData: any = { data: [], lookup: {}, }; @@ -838,12 +759,14 @@ class MUIDataTable extends React.Component { // Multiple row selection customization if (this.options.rowsSelected && this.options.rowsSelected.length && this.options.selectableRows === 'multiple') { this.options.rowsSelected - .filter((selectedRowIndex) => selectedRowIndex === 0 || (Number(selectedRowIndex) && selectedRowIndex > 0)) - .forEach((row) => { + .filter( + (selectedRowIndex: any) => selectedRowIndex === 0 || (Number(selectedRowIndex) && selectedRowIndex > 0), + ) + .forEach((row: any) => { let rowPos = row; for (let cIndex = 0; cIndex < this.state.displayData.length; cIndex++) { - if (this.state.displayData[cIndex].dataIndex === row) { + if (this.state.displayData[cIndex]!.dataIndex === row) { rowPos = cIndex; break; } @@ -862,7 +785,7 @@ class MUIDataTable extends React.Component { let rowPos = this.options.rowsSelected[0]; for (let cIndex = 0; cIndex < this.state.displayData.length; cIndex++) { - if (this.state.displayData[cIndex].dataIndex === this.options.rowsSelected[0]) { + if (this.state.displayData[cIndex]!.dataIndex === this.options.rowsSelected[0]) { rowPos = cIndex; break; } @@ -885,11 +808,11 @@ class MUIDataTable extends React.Component { } if (this.options.rowsExpanded && this.options.rowsExpanded.length && this.options.expandableRows) { - this.options.rowsExpanded.forEach((row) => { + this.options.rowsExpanded.forEach((row: any) => { let rowPos = row; for (let cIndex = 0; cIndex < this.state.displayData.length; cIndex++) { - if (this.state.displayData[cIndex].dataIndex === row) { + if (this.state.displayData[cIndex]!.dataIndex === row) { rowPos = cIndex; break; } @@ -936,15 +859,15 @@ class MUIDataTable extends React.Component { * Build the table data used to display to the user (ie: after filter/search applied) */ computeDisplayRow( - columns, - row, - rowIndex, - filterList, - searchText, - dataForTableMeta, - options, - props, - currentTableData, + columns: any[], + row: any[], + rowIndex: number, + filterList: any[], + searchText: string | null, + dataForTableMeta: any, + options: any, + props: any, + currentTableData: any, ) { let isFiltered = false; let isSearchFound = false; @@ -1011,7 +934,7 @@ class MUIDataTable extends React.Component { } else if (filterType !== 'textField' && Array.isArray(columnValue)) { if (options.filterArrayFullMatch) { //true if every filterVal exists in columnVal, false otherwise - const isFullMatch = filterVal.every((el) => { + const isFullMatch = filterVal.every((el: any) => { return columnValue.indexOf(el) >= 0; }); //if it is not a fullMatch, filter row out @@ -1019,7 +942,7 @@ class MUIDataTable extends React.Component { isFiltered = true; } } else { - const isAnyMatch = filterVal.some((el) => { + const isAnyMatch = filterVal.some((el: any) => { return columnValue.indexOf(el) >= 0; }); //if no value matches, filter row out @@ -1064,7 +987,7 @@ class MUIDataTable extends React.Component { else return displayRow; } - hasSearchText = (toSearch, toFind, caseSensitive) => { + hasSearchText = (toSearch: any, toFind: any, caseSensitive: boolean) => { let stack = toSearch.toString(); let needle = toFind.toString(); @@ -1076,7 +999,7 @@ class MUIDataTable extends React.Component { return stack.indexOf(needle) >= 0; }; - updateDataCol = (row, index, value) => { + updateDataCol = (row: number, index: number, value: any) => { this.setState((prevState) => { let changedData = cloneDeep(prevState.data); let filterData = cloneDeep(prevState.filterData); @@ -1090,21 +1013,21 @@ class MUIDataTable extends React.Component { prevState, prevState.data, ); - const funcResult = prevState.columns[index].customBodyRender(value, tableMeta); + const funcResult = (prevState.columns[index] as any).customBodyRender(value, tableMeta); const filterValue = - React.isValidElement(funcResult) && funcResult.props.value - ? funcResult.props.value - : prevState['data'][row][index]; + React.isValidElement(funcResult) && (funcResult.props as any).value + ? (funcResult.props as any).value + : (prevState['data'] as any)[row][index]; - const prevFilterIndex = filterData[index].indexOf(filterValue); - filterData[index].splice(prevFilterIndex, 1, filterValue); + const prevFilterIndex = filterData[index]!.indexOf(filterValue); + filterData[index]!.splice(prevFilterIndex, 1, filterValue); - changedData[row].data[index] = value; + changedData[row]!.data[index] = value; if (this.options.sortFilterList) { const comparator = getCollatorComparator(); - filterData[index].sort(comparator); + filterData[index]!.sort(comparator); } return { @@ -1122,7 +1045,15 @@ class MUIDataTable extends React.Component { }); }; - getTableMeta = (rowIndex, colIndex, rowData, columnData, tableData, curState, currentTableData) => { + getTableMeta = ( + rowIndex: any, + colIndex: any, + rowData: any, + columnData: any, + tableData: any, + curState: any, + currentTableData: any, + ) => { const { columns, data, displayData, filterData, ...tableState } = curState; return { @@ -1136,7 +1067,7 @@ class MUIDataTable extends React.Component { }; }; - getDisplayData(columns, data, filterList, searchText, tableMeta, props) { + getDisplayData(columns: any[], data: any[], filterList: any[], searchText: any, tableMeta: any, props: any) { let newRows = []; const dataForTableMeta = tableMeta ? tableMeta.tableData : props.data; @@ -1164,11 +1095,11 @@ class MUIDataTable extends React.Component { return newRows; } - toggleViewColumn = (index) => { + toggleViewColumn = (index: number) => { this.setState( (prevState) => { const columns = cloneDeep(prevState.columns); - columns[index].display = columns[index].display === 'true' ? 'false' : 'true'; + columns[index]!['display'] = columns[index]!['display'] === 'true' ? 'false' : 'true'; return { columns: columns, }; @@ -1178,13 +1109,13 @@ class MUIDataTable extends React.Component { var cb = this.options.onViewColumnsChange || this.options.onColumnViewChange; if (cb) { - cb(this.state.columns[index].name, this.state.columns[index].display === 'true' ? 'add' : 'remove'); + cb(this.state.columns[index]!['name'], this.state.columns[index]!['display'] === 'true' ? 'add' : 'remove'); } }, ); }; - updateColumns = (newColumns) => { + updateColumns = (newColumns: any[]) => { this.setState( (prevState) => { return { @@ -1202,7 +1133,7 @@ class MUIDataTable extends React.Component { ); }; - getSortDirectionLabel(sortOrder) { + getSortDirectionLabel(sortOrder: any) { switch (sortOrder.direction) { case 'asc': return 'ascending'; @@ -1219,45 +1150,46 @@ class MUIDataTable extends React.Component { const { classes } = this.props; const tableProps = this.options.setTableProps() || {}; - tableProps.className = clsx(classes.tableRoot, tableProps.className); + tableProps.className = clsx(classes['tableRoot'], tableProps.className); return tableProps; } - toggleSortColumn = (index) => { + toggleSortColumn = (index: number) => { flushSync(() => { this.setState( (prevState) => { let columns = cloneDeep(prevState.columns); let data = prevState.data; - let newOrder = columns[index].sortDescFirst ? 'desc' : 'asc'; // default + const column = columns[index]!; + let newOrder = column['sortDescFirst'] ? 'desc' : 'asc'; // default let sequenceOrder = ['asc', 'desc']; - if (columns[index].sortDescFirst) { + if (column['sortDescFirst']) { sequenceOrder = ['desc', 'asc']; } - if (columns[index].sortThirdClickReset) { + if (column['sortThirdClickReset']) { sequenceOrder.push('none'); } - if (columns[index].name === this.state.sortOrder.name) { - let pos = sequenceOrder.indexOf(this.state.sortOrder.direction); + if (column['name'] === this.state.sortOrder['name']) { + let pos = sequenceOrder.indexOf(this.state.sortOrder['direction'] as string); if (pos !== -1) { pos++; if (pos >= sequenceOrder.length) pos = 0; - newOrder = sequenceOrder[pos]; + newOrder = sequenceOrder[pos]!; } } const newSortOrder = { - name: columns[index].name, + name: column['name'], direction: newOrder, }; const orderLabel = this.getSortDirectionLabel(newSortOrder); - const announceText = `Table now sorted by ${columns[index].name} : ${orderLabel}`; + const announceText = `Table now sorted by ${column['name']} : ${orderLabel}`; - let newState = { + let newState: any = { columns: columns, announceText: announceText, activeColumn: index, @@ -1272,7 +1204,7 @@ class MUIDataTable extends React.Component { sortOrder: newSortOrder, }; } else { - const sortedData = this.sortTable(data, index, newOrder, columns[index].sortCompare); + const sortedData = this.sortTable(data, index, newOrder, column['sortCompare']); newState = { ...newState, @@ -1297,14 +1229,14 @@ class MUIDataTable extends React.Component { this.setTableAction('sort'); if (this.options.onColumnSortChange) { - this.options.onColumnSortChange(this.state.sortOrder.name, this.state.sortOrder.direction); + this.options.onColumnSortChange(this.state.sortOrder['name'], this.state.sortOrder['direction']); } }, ); }); }; - changeRowsPerPage = (rows) => { + changeRowsPerPage = (rows: number) => { const rowCount = this.options.count || this.state.displayData.length; flushSync(() => { @@ -1324,7 +1256,7 @@ class MUIDataTable extends React.Component { }); }; - changePage = (page) => { + changePage = (page: number) => { flushSync(() => { this.setState( () => ({ @@ -1357,7 +1289,7 @@ class MUIDataTable extends React.Component { ); }; - searchTextUpdate = (text) => { + searchTextUpdate = (text: string) => { this.setState( (prevState) => ({ searchText: text && text.length ? text : null, @@ -1403,8 +1335,8 @@ class MUIDataTable extends React.Component { ); }; - updateFilterByType = (filterList, index, value, type, customUpdate) => { - const filterPos = filterList[index].findIndex((filter) => JSON.stringify(filter) === JSON.stringify(value)); + updateFilterByType = (filterList: any, index: number, value: any, type: string, customUpdate: any) => { + const filterPos = filterList[index].findIndex((filter: any) => JSON.stringify(filter) === JSON.stringify(value)); switch (type) { case 'checkbox': @@ -1431,7 +1363,7 @@ class MUIDataTable extends React.Component { } }; - filterUpdate = (index, value, column, type, customUpdate, next) => { + filterUpdate = (index: number, value: any, column: any, type: string, customUpdate: any, next: any) => { this.setState( (prevState) => { const filterList = cloneDeep(prevState.filterList); @@ -1472,7 +1404,7 @@ class MUIDataTable extends React.Component { if (expandedRowsData.length > 0) { // collapse all for (let ii = expandedRowsData.length - 1; ii >= 0; ii--) { - let item = expandedRowsData[ii]; + let item = expandedRowsData[ii]!; if (!isRowExpandable || (isRowExpandable && isRowExpandable(item.dataIndex, this.state.expandedRows))) { affecttedRows.push(expandedRowsData.splice(ii, 1)); } @@ -1480,8 +1412,11 @@ class MUIDataTable extends React.Component { } else { // expand all for (let ii = 0; ii < this.state.data.length; ii++) { - let item = this.state.data[ii]; - if (!isRowExpandable || (isRowExpandable && isRowExpandable(item.dataIndex, this.state.expandedRows))) { + let item = this.state.data[ii]!; + if ( + !isRowExpandable || + (isRowExpandable && isRowExpandable((item as any).dataIndex, this.state.expandedRows)) + ) { if (this.state.expandedRows.lookup[item.index] !== true) { let newItem = { index: ii, @@ -1518,7 +1453,7 @@ class MUIDataTable extends React.Component { return this.state.expandedRows.data.length === this.state.data.length; }; - updateColumnOrder = (columnOrder, columnIndex, newPosition) => { + updateColumnOrder = (columnOrder: number[], columnIndex: number, newPosition: number) => { this.setState( (prevState) => { return { @@ -1566,17 +1501,17 @@ class MUIDataTable extends React.Component { ); }; - toggleExpandRow = (row) => { + toggleExpandRow = (row: any) => { const { dataIndex } = row; const { isRowExpandable } = this.options; let { expandedRows } = this.state; const expandedRowsData = [...expandedRows.data]; let shouldCollapseExpandedRow = false; let hasRemovedRow = false; - let removedRow = []; + let removedRow: any[] = []; for (var cIndex = 0; cIndex < expandedRowsData.length; cIndex++) { - if (expandedRowsData[cIndex].dataIndex === dataIndex) { + if (expandedRowsData[cIndex]!.dataIndex === dataIndex) { shouldCollapseExpandedRow = true; break; } @@ -1610,7 +1545,7 @@ class MUIDataTable extends React.Component { ); }; - selectRowUpdate = (type, value, shiftAdjacentRows = []) => { + selectRowUpdate = (type: string, value: any, shiftAdjacentRows: any[] = []) => { // safety check const { selectableRows } = this.options; if (selectableRows === 'none') { @@ -1626,9 +1561,9 @@ class MUIDataTable extends React.Component { let isDeselect = selectedRowsLen === displayData.length || (selectedRowsLen < displayData.length && selectedRowsLen > 0); - let selectedRows = displayData.reduce((arr, d, i) => { - const selected = isRowSelectable ? isRowSelectable(displayData[i].dataIndex, prevSelectedRows) : true; - selected && arr.push({ index: i, dataIndex: displayData[i].dataIndex }); + let selectedRows: any[] = displayData.reduce((arr: any[], d: any, i: number) => { + const selected = isRowSelectable ? isRowSelectable(displayData[i]!.dataIndex, prevSelectedRows) : true; + selected && arr.push({ index: i, dataIndex: displayData[i]!.dataIndex }); return arr; }, []); @@ -1641,7 +1576,7 @@ class MUIDataTable extends React.Component { isDeselect = true; } else { for (let ii = 0; ii < displayData.length; ii++) { - if (!selectedMap[displayData[ii].dataIndex]) { + if (!selectedMap[displayData[ii]!.dataIndex]) { isDeselect = true; } } @@ -1687,7 +1622,7 @@ class MUIDataTable extends React.Component { let rowPos = -1; for (let cIndex = 0; cIndex < selectedRows.length; cIndex++) { - if (selectedRows[cIndex].dataIndex === dataIndex) { + if (selectedRows[cIndex]!.dataIndex === dataIndex) { rowPos = cIndex; break; } @@ -1700,7 +1635,7 @@ class MUIDataTable extends React.Component { if (shiftAdjacentRows.length > 0) { let shiftAdjacentMap = buildMap(shiftAdjacentRows); for (let cIndex = selectedRows.length - 1; cIndex >= 0; cIndex--) { - if (shiftAdjacentMap[selectedRows[cIndex].dataIndex]) { + if (shiftAdjacentMap[selectedRows[cIndex]!.dataIndex]) { selectedRows.splice(cIndex, 1); } } @@ -1750,7 +1685,7 @@ class MUIDataTable extends React.Component { } else if (type === 'custom') { const { displayData } = this.state; - const data = value.map((row) => ({ index: row, dataIndex: displayData[row].dataIndex })); + const data = value.map((row: any) => ({ index: row, dataIndex: displayData[row]!.dataIndex })); const lookup = buildMap(data); this.setState( @@ -1778,7 +1713,7 @@ class MUIDataTable extends React.Component { } }; - sortTable(data, col, order, columnSortCompare = null) { + sortTable(data: any[], col: number, order: string, columnSortCompare: any = null) { let hasCustomTableSort = this.options.customSort && !columnSortCompare; let meta = { selectedRows: this.state.selectedRows }; // meta for customSort let dataSrc = hasCustomTableSort @@ -1794,7 +1729,7 @@ class MUIDataTable extends React.Component { }, []); } - let sortedData = dataSrc.map((row, sIndex) => ({ + let sortedData = dataSrc.map((row: any, sIndex: number) => ({ data: row.data[col], rowData: row.data, position: sIndex, @@ -1826,7 +1761,7 @@ class MUIDataTable extends React.Component { }; } - render() { + override render() { const { classes, className, @@ -1840,7 +1775,7 @@ class MUIDataTable extends React.Component { TableToolbar, TableToolbarSelect, DragDropBackend = HTML5Backend, - }, + } = {} as any, } = this.props; const { announceText, @@ -1872,53 +1807,53 @@ class MUIDataTable extends React.Component { const rowsPerPage = this.options.pagination ? this.state.rowsPerPage : displayData.length; const showToolbar = hasToolbarItem(this.options, title); const columnNames = columns.map((column) => ({ - name: column.name, - filterType: column.filterType || this.options.filterType, + name: column['name'], + filterType: column['filterType'] || this.options.filterType, })); const responsiveOption = this.options.responsive; - let paperClasses = `${classes.paper} ${className}`; + let paperClasses = `${classes['paper']} ${className}`; let maxHeight = this.options.tableBodyMaxHeight; let responsiveClass; switch (responsiveOption) { // deprecated case 'scroll': - responsiveClass = classes.responsiveScroll; + responsiveClass = classes['responsiveScroll']; maxHeight = '499px'; break; // deprecated case 'scrollMaxHeight': - responsiveClass = classes.responsiveScrollMaxHeight; + responsiveClass = classes['responsiveScrollMaxHeight']; maxHeight = '499px'; break; // deprecated case 'scrollFullHeight': - responsiveClass = classes.responsiveScrollFullHeight; + responsiveClass = classes['responsiveScrollFullHeight']; maxHeight = 'none'; break; // deprecated case 'scrollFullHeightFullWidth': - responsiveClass = classes.responsiveScrollFullHeight; - paperClasses = `${classes.paperResponsiveScrollFullHeightFullWidth} ${className}`; + responsiveClass = classes['responsiveScrollFullHeight']; + paperClasses = `${classes['paperResponsiveScrollFullHeightFullWidth']} ${className}`; break; // deprecated case 'stacked': - responsiveClass = classes.responsiveStacked; + responsiveClass = classes['responsiveStacked']; maxHeight = 'none'; break; // deprecated case 'stackedFullWidth': - responsiveClass = classes.responsiveStackedFullWidth; - paperClasses = `${classes.paperResponsiveScrollFullHeightFullWidth} ${className}`; + responsiveClass = classes['responsiveStackedFullWidth']; + paperClasses = `${classes['paperResponsiveScrollFullHeightFullWidth']} ${className}`; maxHeight = 'none'; break; default: - responsiveClass = classes.responsiveBase; + responsiveClass = classes['responsiveBase']; break; } - var tableHeightVal = {}; + var tableHeightVal: any = {}; if (maxHeight) { tableHeightVal.maxHeight = maxHeight; } @@ -1927,16 +1862,16 @@ class MUIDataTable extends React.Component { } const tableProps = this.options.setTableProps ? this.options.setTableProps() || {} : {}; - const tableClassNames = clsx(classes.tableRoot, tableProps.className); + const tableClassNames = clsx(classes['tableRoot'], tableProps.className); delete tableProps.className; // remove className from props to avoid the className being applied twice - const dndProps = {}; + const dndProps: any = {}; if (typeof window !== 'undefined') { dndProps.context = window; } return ( - + {(this.options.selectToolbarPlacement === STP.ALWAYS || (selectedRows.data.length > 0 && this.options.selectToolbarPlacement !== STP.NONE)) && ( { + serverSideFilterList={(this.props.options as any)?.serverSideFilterList} + filterListRenderers={columns.map((c: any) => { if (c.customFilterListOptions && c.customFilterListOptions.render) return c.customFilterListOptions.render; // DEPRECATED: This option is being replaced with customFilterListOptions.render if (c.customFilterListRender) return c.customFilterListRender; - return (f) => f; + return (f: any) => f; })} - customFilterListUpdate={columns.map((c) => { + customFilterListUpdate={columns.map((c: any) => { return c.customFilterListOptions && c.customFilterListOptions.update ? c.customFilterListOptions.update : null; @@ -1998,8 +1933,8 @@ class MUIDataTable extends React.Component { (this.updateDividers = fn)} - setResizeable={(fn) => (this.setHeadResizeable = fn)} + updateDividers={(fn: any) => (this.updateDividers = fn)} + setResizeable={(fn: any) => (this.setHeadResizeable = fn)} options={this.props.options} tableId={this.options.tableId} /> @@ -2007,12 +1942,12 @@ class MUIDataTable extends React.Component { {(() => { const components = ( (this.tableRef = el)} + ref={(el: any) => (this.tableRef = el)} tabIndex={'0'} role={'grid'} className={tableClassNames} {...tableProps}> - {title} + {title} -
+
{announceText}
@@ -2092,4 +2027,4 @@ class MUIDataTable extends React.Component { } } -export default withStyles(MUIDataTable, defaultTableStyles, { name: 'MUIDataTable' }); +export default withStyles(MUIDataTable, defaultTableStyles as any, { name: 'MUIDataTable' }); diff --git a/src/components/ExpandButton.js b/src/components/ExpandButton.tsx similarity index 68% rename from src/components/ExpandButton.js rename to src/components/ExpandButton.tsx index cc589936..5ff7b988 100644 --- a/src/components/ExpandButton.js +++ b/src/components/ExpandButton.tsx @@ -2,6 +2,19 @@ import React from 'react'; import { IconButton } from '@mui/material'; import { KeyboardArrowRight as KeyboardArrowRightIcon, Remove as RemoveIcon } from '@mui/icons-material'; +import type { MUIDataTableExpandedRows } from '../types/options'; + +interface ExpandButtonProps { + areAllRowsExpanded: () => boolean; + buttonClass: string; + expandableRowsHeader: boolean; + expandedRows: MUIDataTableExpandedRows; + iconClass: string; + iconIndeterminateClass: string; + isHeaderCell: boolean; + onExpand: () => void; +} + const ExpandButton = ({ areAllRowsExpanded, buttonClass, @@ -11,10 +24,10 @@ const ExpandButton = ({ iconIndeterminateClass, isHeaderCell, onExpand, -}) => { +}: ExpandButtonProps) => { return ( <> - {isHeaderCell && !areAllRowsExpanded() && areAllRowsExpanded && expandedRows.data.length > 0 ? ( + {isHeaderCell && !areAllRowsExpanded() && expandedRows.data.length > 0 ? ( ({ root: { @@ -12,7 +14,6 @@ const useStyles = makeStyles({ name: 'MUIDataTableJumpToPage' })((theme) => ({ caption: { flexShrink: 0, }, - /*  Styles applied to the Select component root element */ selectRoot: { marginRight: 32, marginLeft: 8, @@ -26,36 +27,43 @@ const useStyles = makeStyles({ name: 'MUIDataTableJumpToPage' })((theme) => ({ textAlignLast: 'right', fontSize: theme.typography.pxToRem(14), }, - /* Styles applied to Select component icon class */ selectIcon: {}, - /* Styles applied to InputBase component */ input: { - color: 'inhert', - fontSize: 'inhert', + color: 'inherit', + fontSize: 'inherit', flexShrink: 0, }, + menuItem: {}, })); -function JumpToPage(props) { +interface JumpToPageProps { + count: number; + page: number; + rowsPerPage: number; + textLabels: MUIDataTableTextLabels; + changePage: (page: number) => void; +} + +function JumpToPage(props: JumpToPageProps) { const { classes } = useStyles(); - const handlePageChange = (event) => { - props.changePage(parseInt(event.target.value, 10)); + const handlePageChange = (event: SelectChangeEvent) => { + props.changePage(event.target.value); }; - const { count, textLabels, rowsPerPage, page, changePage } = props; + const { count, textLabels, rowsPerPage, page } = props; const textLabel = textLabels.pagination.jumpToPage; - let pages = []; - let lastPage = Math.min(1000, getPageValue(count, rowsPerPage, 1000000)); + const pages: number[] = []; + const lastPage = Math.min(1000, getPageValue(count, rowsPerPage, 1000000)); for (let ii = 0; ii <= lastPage; ii++) { pages.push(ii); } const MenuItemComponent = MenuItem; - let myStyle = { + const myStyle = { display: 'flex', minHeight: '52px', alignItems: 'center', @@ -82,11 +90,4 @@ function JumpToPage(props) { ); } -JumpToPage.propTypes = { - count: PropTypes.number.isRequired, - page: PropTypes.number.isRequired, - rowsPerPage: PropTypes.number.isRequired, - textLabels: PropTypes.object.isRequired, -}; - export default JumpToPage; diff --git a/src/components/Popover.js b/src/components/Popover.tsx similarity index 53% rename from src/components/Popover.js rename to src/components/Popover.tsx index 1dd29186..76c0deff 100644 --- a/src/components/Popover.js +++ b/src/components/Popover.tsx @@ -1,11 +1,22 @@ import React, { useEffect, useRef, useState } from 'react'; -import PropTypes from 'prop-types'; import { Popover as MuiPopover, IconButton } from '@mui/material'; import { Close as CloseIcon } from '@mui/icons-material'; +import type { ReactNode } from 'react'; +import type { PopoverOrigin } from '@mui/material/Popover'; -const Popover = ({ className, trigger, refExit, hide, content, ...providedProps }) => { +interface PopoverProps { + className?: string; + trigger: ReactNode; + refExit?: () => void; + hide?: boolean; + content: ReactNode; + classes?: Record; + [key: string]: unknown; +} + +const Popover = ({ className, trigger, refExit, hide, content, ...providedProps }: PopoverProps) => { const [isOpen, open] = useState(false); - const anchorEl = useRef(null); + const anchorEl = useRef(null); useEffect(() => { if (isOpen) { @@ -16,7 +27,7 @@ const Popover = ({ className, trigger, refExit, hide, content, ...providedProps } }, [hide, isOpen, open]); - const handleClick = (event) => { + const handleClick = (event: React.MouseEvent) => { anchorEl.current = event.currentTarget; open(true); }; @@ -25,15 +36,20 @@ const Popover = ({ className, trigger, refExit, hide, content, ...providedProps open(false); }; - const closeIconClass = providedProps.classes.closeIcon; - delete providedProps.classes.closeIcon; // remove non-standard class from being passed to the popover component + const classes = (providedProps.classes || {}) as Record; + const closeIconClass = classes['closeIcon']; + const passedProps = { ...providedProps }; + if (passedProps.classes && typeof passedProps.classes === 'object') { + const { closeIcon, ...restClasses } = passedProps.classes as Record; + passedProps.classes = restClasses; + } - const transformOriginSpecs = { + const transformOriginSpecs: PopoverOrigin = { vertical: 'top', horizontal: 'center', }; - const anchorOriginSpecs = { + const anchorOriginSpecs: PopoverOrigin = { vertical: 'bottom', horizontal: 'center', }; @@ -45,8 +61,10 @@ const Popover = ({ className, trigger, refExit, hide, content, ...providedProps }; const triggerProps = { - onClick: (event) => { - if (trigger.props.onClick) trigger.props.onClick(); + onClick: (event: React.MouseEvent) => { + if (React.isValidElement(trigger)) { + (trigger.props as { onClick?: (event: React.MouseEvent) => void }).onClick?.(event); + } handleClick(event); }, }; @@ -59,17 +77,21 @@ const Popover = ({ className, trigger, refExit, hide, content, ...providedProps + {...passedProps}> + style={{ position: 'absolute', right: '4px', top: '4px', zIndex: 1000 }}> {content} @@ -78,11 +100,4 @@ const Popover = ({ className, trigger, refExit, hide, content, ...providedProps ); }; -Popover.propTypes = { - refExit: PropTypes.func, - trigger: PropTypes.node.isRequired, - content: PropTypes.node.isRequired, - hide: PropTypes.bool, -}; - export default Popover; diff --git a/src/components/TableBody.js b/src/components/TableBody.js deleted file mode 100644 index a6bc4659..00000000 --- a/src/components/TableBody.js +++ /dev/null @@ -1,337 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Typography, TableBody as MuiTableBody } from '@mui/material'; -import TableBodyCell from './TableBodyCell'; -import TableBodyRow from './TableBodyRow'; -import TableSelectCell from './TableSelectCell'; -import { withStyles } from 'tss-react/mui'; -import cloneDeep from 'lodash.clonedeep'; -import { getPageValue } from '../utils'; -import clsx from 'clsx'; - -const defaultBodyStyles = (theme) => ({ - root: {}, - emptyTitle: { - textAlign: 'center', - }, - lastStackedCell: { - [theme.breakpoints.down('md')]: { - '& td:last-child': { - borderBottom: 'none', - }, - }, - }, - lastSimpleCell: { - [theme.breakpoints.down('sm')]: { - '& td:last-child': { - borderBottom: 'none', - }, - }, - }, -}); - -class TableBody extends React.Component { - static propTypes = { - /** Data used to describe table */ - data: PropTypes.array.isRequired, - /** Total number of data rows */ - count: PropTypes.number.isRequired, - /** Columns used to describe table */ - columns: PropTypes.array.isRequired, - /** Options used to describe table */ - options: PropTypes.object.isRequired, - /** Data used to filter table against */ - filterList: PropTypes.array, - /** Callback to execute when row is clicked */ - onRowClick: PropTypes.func, - /** Table rows expanded */ - expandedRows: PropTypes.object, - /** Table rows selected */ - selectedRows: PropTypes.object, - /** Callback to trigger table row select */ - selectRowUpdate: PropTypes.func, - /** The most recent row to have been selected/unselected */ - previousSelectedRow: PropTypes.object, - /** Data used to search table against */ - searchText: PropTypes.string, - /** Toggle row expandable */ - toggleExpandRow: PropTypes.func, - /** Extend the style applied to components */ - classes: PropTypes.object, - }; - - static defaultProps = { - toggleExpandRow: () => {}, - }; - - buildRows() { - const { data, page, rowsPerPage, count } = this.props; - - if (this.props.options.serverSide) return data.length ? data : null; - - let rows = []; - const highestPageInRange = getPageValue(count, rowsPerPage, page); - const fromIndex = highestPageInRange === 0 ? 0 : highestPageInRange * rowsPerPage; - const toIndex = Math.min(count, (highestPageInRange + 1) * rowsPerPage); - - if (page > highestPageInRange) { - console.warn('Current page is out of range, using the highest page that is in range instead.'); - } - - for (let rowIndex = fromIndex; rowIndex < count && rowIndex < toIndex; rowIndex++) { - if (data[rowIndex] !== undefined) rows.push(data[rowIndex]); - } - - return rows.length ? rows : null; - } - - getRowIndex(index) { - const { page, rowsPerPage, options } = this.props; - - if (options.serverSide) { - return index; - } - - const startIndex = page === 0 ? 0 : page * rowsPerPage; - return startIndex + index; - } - - isRowSelected(dataIndex) { - const { selectedRows } = this.props; - return selectedRows.lookup && selectedRows.lookup[dataIndex] ? true : false; - } - - isRowExpanded(dataIndex) { - const { expandedRows } = this.props; - return expandedRows.lookup && expandedRows.lookup[dataIndex] ? true : false; - } - - isRowSelectable(dataIndex, selectedRows) { - const { options } = this.props; - selectedRows = selectedRows || this.props.selectedRows; - - if (options.isRowSelectable) { - return options.isRowSelectable(dataIndex, selectedRows); - } else { - return true; - } - } - - isRowExpandable(dataIndex) { - const { options, expandedRows } = this.props; - if (options.isRowExpandable) { - return options.isRowExpandable(dataIndex, expandedRows); - } else { - return true; - } - } - - handleRowSelect = (data, event) => { - let shiftKey = event && event.nativeEvent ? event.nativeEvent.shiftKey : false; - let shiftAdjacentRows = []; - let previousSelectedRow = this.props.previousSelectedRow; - - // If the user is pressing shift and has previously clicked another row. - if (shiftKey && previousSelectedRow && previousSelectedRow.index < this.props.data.length) { - let curIndex = previousSelectedRow.index; - - // Create a copy of the selectedRows object. This will be used and modified - // below when we see if we can add adjacent rows. - let selectedRows = cloneDeep(this.props.selectedRows); - - // Add the clicked on row to our copy of selectedRows (if it isn't already present). - let clickedDataIndex = this.props.data[data.index].dataIndex; - if (selectedRows.data.filter((d) => d.dataIndex === clickedDataIndex).length === 0) { - selectedRows.data.push({ - index: data.index, - dataIndex: clickedDataIndex, - }); - selectedRows.lookup[clickedDataIndex] = true; - } - - while (curIndex !== data.index) { - let dataIndex = this.props.data[curIndex].dataIndex; - - if (this.isRowSelectable(dataIndex, selectedRows)) { - let lookup = { - index: curIndex, - dataIndex: dataIndex, - }; - - // Add adjacent row to temp selectedRow object if it isn't present. - if (selectedRows.data.filter((d) => d.dataIndex === dataIndex).length === 0) { - selectedRows.data.push(lookup); - selectedRows.lookup[dataIndex] = true; - } - - shiftAdjacentRows.push(lookup); - } - curIndex = data.index > curIndex ? curIndex + 1 : curIndex - 1; - } - } - this.props.selectRowUpdate('cell', data, shiftAdjacentRows); - }; - - handleRowClick = (row, data, event) => { - // Don't trigger onRowClick if the event was actually the expandable icon. - if ( - event.target.id === 'expandable-button' || - (event.target.nodeName === 'path' && event.target.parentNode.id === 'expandable-button') - ) { - return; - } - - // Don't trigger onRowClick if the event was actually a row selection via checkbox - if (event.target.id && event.target.id.startsWith('MUIDataTableSelectCell')) return; - - // Check if we should toggle row select when row is clicked anywhere - if ( - this.props.options.selectableRowsOnClick && - this.props.options.selectableRows !== 'none' && - this.isRowSelectable(data.dataIndex, this.props.selectedRows) - ) { - const selectRow = { index: data.rowIndex, dataIndex: data.dataIndex }; - this.handleRowSelect(selectRow, event); - } - // Check if we should trigger row expand when row is clicked anywhere - if ( - this.props.options.expandableRowsOnClick && - this.props.options.expandableRows && - this.isRowExpandable(data.dataIndex, this.props.expandedRows) - ) { - const expandRow = { index: data.rowIndex, dataIndex: data.dataIndex }; - this.props.toggleExpandRow(expandRow); - } - - // Don't trigger onRowClick if the event was actually a row selection via click - if (this.props.options.selectableRowsOnClick) return; - - this.props.options.onRowClick && this.props.options.onRowClick(row, data, event); - }; - - processRow = (row, columnOrder) => { - let ret = []; - for (let ii = 0; ii < row.length; ii++) { - ret.push({ - value: row[columnOrder[ii]], - index: columnOrder[ii], - }); - } - return ret; - }; - - render() { - const { - classes, - columns, - toggleExpandRow, - options, - columnOrder = this.props.columns.map((item, idx) => idx), - components = {}, - tableId, - } = this.props; - const tableRows = this.buildRows(); - const visibleColCnt = columns.filter((c) => c.display === 'true').length; - - return ( - - {tableRows && tableRows.length > 0 ? ( - tableRows.map((data, rowIndex) => { - const { data: row, dataIndex } = data; - - if (options.customRowRender) { - return options.customRowRender(row, dataIndex, rowIndex); - } - - let isRowSelected = options.selectableRows !== 'none' ? this.isRowSelected(dataIndex) : false; - let isRowSelectable = this.isRowSelectable(dataIndex); - let bodyClasses = options.setRowProps ? options.setRowProps(row, dataIndex, rowIndex) || {} : {}; - - const processedRow = this.processRow(row, columnOrder); - - return ( - - - - {processedRow.map( - (column) => - columns[column.index].display === 'true' && ( - - {column.value} - - ), - )} - - {this.isRowExpanded(dataIndex) && options.renderExpandableRow(row, { rowIndex, dataIndex })} - - ); - }) - ) : ( - - - - {options.textLabels.body.noMatch} - - - - )} - - ); - } -} - -export default withStyles(TableBody, defaultBodyStyles, { name: 'MUIDataTableBody' }); diff --git a/src/components/TableBody.tsx b/src/components/TableBody.tsx new file mode 100644 index 00000000..53c7fed0 --- /dev/null +++ b/src/components/TableBody.tsx @@ -0,0 +1,325 @@ +import React from 'react'; +import { Typography, TableBody as MuiTableBody } from '@mui/material'; +import TableBodyCell from './TableBodyCell'; +import TableBodyRow from './TableBodyRow'; +import TableSelectCell from './TableSelectCell'; +import { withStyles } from 'tss-react/mui'; +import cloneDeep from 'lodash.clonedeep'; +import { getPageValue } from '../utils'; +import clsx from 'clsx'; +import type { ComponentType } from 'react'; +import type { CSSObject } from 'tss-react'; +import type { MUIDataTableColumnState } from '../types/columns'; +import type { + MUIDataTableOptions, + MUIDataTableSelectedRows, + MUIDataTableExpandedRows, + MUIDataTableDisplayRow, + MUIDataTableSelectedRow, + MUIDataTableCellValue, +} from '../types/options'; +import type { Theme } from '@mui/material/styles'; + +const defaultBodyStyles = (theme: Theme) => + ({ + root: {}, + emptyTitle: { + textAlign: 'center', + }, + lastStackedCell: { + [theme.breakpoints.down('md')]: { + '& td:last-child': { + borderBottom: 'none', + }, + }, + }, + lastSimpleCell: { + [theme.breakpoints.down('sm')]: { + '& td:last-child': { + borderBottom: 'none', + }, + }, + }, + }) satisfies Record; + +export interface TableBodyProps { + data: MUIDataTableDisplayRow[]; + count: number; + columns: MUIDataTableColumnState[]; + options: MUIDataTableOptions; + filterList?: string[][]; + expandedRows: MUIDataTableExpandedRows; + selectedRows: MUIDataTableSelectedRows; + selectRowUpdate: (type: string, data: unknown, adjacentRows?: MUIDataTableSelectedRow[]) => void; + previousSelectedRow: MUIDataTableSelectedRow | null; + searchText?: string | null; + toggleExpandRow: (row: { index: number; dataIndex: number }) => void; + classes: Record, string>; + columnOrder?: number[]; + components?: { Checkbox?: ComponentType; ExpandButton?: ComponentType }; + tableId?: string; + page: number; + rowsPerPage: number; +} + +class TableBody extends React.Component { + static defaultProps = { + toggleExpandRow: () => {}, + }; + + buildRows(): MUIDataTableDisplayRow[] | null { + const { data, page, rowsPerPage, count } = this.props; + + if (this.props.options.serverSide) return data.length ? data : null; + + const rows: MUIDataTableDisplayRow[] = []; + const highestPageInRange = getPageValue(count, rowsPerPage, page); + const fromIndex = highestPageInRange === 0 ? 0 : highestPageInRange * rowsPerPage; + const toIndex = Math.min(count, (highestPageInRange + 1) * rowsPerPage); + + if (page > highestPageInRange) { + console.warn('Current page is out of range, using the highest page that is in range instead.'); + } + + for (let rowIndex = fromIndex; rowIndex < count && rowIndex < toIndex; rowIndex++) { + const row = data[rowIndex]; + if (row !== undefined) rows.push(row); + } + + return rows.length ? rows : null; + } + + getRowIndex(index: number): number { + const { page, rowsPerPage, options } = this.props; + if (options.serverSide) return index; + const startIndex = page === 0 ? 0 : page * rowsPerPage; + return startIndex + index; + } + + isRowSelected(dataIndex: number): boolean { + const { selectedRows } = this.props; + return selectedRows.lookup && selectedRows.lookup[dataIndex] ? true : false; + } + + isRowExpanded(dataIndex: number): boolean { + const { expandedRows } = this.props; + return expandedRows.lookup && expandedRows.lookup[dataIndex] ? true : false; + } + + isRowSelectable(dataIndex: number, selectedRows?: MUIDataTableSelectedRows): boolean { + const { options } = this.props; + const rows = selectedRows || this.props.selectedRows; + if (options.isRowSelectable) { + return options.isRowSelectable(dataIndex, rows); + } + return true; + } + + isRowExpandable(dataIndex: number): boolean { + const { options, expandedRows } = this.props; + if (options.isRowExpandable) { + return options.isRowExpandable(dataIndex, expandedRows); + } + return true; + } + + handleRowSelect = (data: { index: number; dataIndex: number }, event: React.MouseEvent) => { + const shiftKey = event && event.nativeEvent ? (event.nativeEvent as MouseEvent).shiftKey : false; + let shiftAdjacentRows: MUIDataTableSelectedRow[] = []; + const previousSelectedRow = this.props.previousSelectedRow; + + if (shiftKey && previousSelectedRow && previousSelectedRow.index < this.props.data.length) { + let curIndex = previousSelectedRow.index; + const selectedRows = cloneDeep(this.props.selectedRows); + const clickedDataIndex = this.props.data[data.index]?.dataIndex as number; + + if (selectedRows.data.filter((d) => d.dataIndex === clickedDataIndex).length === 0) { + selectedRows.data.push({ index: data.index, dataIndex: clickedDataIndex }); + selectedRows.lookup[clickedDataIndex] = true; + } + + while (curIndex !== data.index) { + const dataIndex = this.props.data[curIndex]?.dataIndex as number; + if (this.isRowSelectable(dataIndex, selectedRows)) { + const lookup = { index: curIndex, dataIndex }; + if (selectedRows.data.filter((d) => d.dataIndex === dataIndex).length === 0) { + selectedRows.data.push(lookup); + selectedRows.lookup[dataIndex] = true; + } + shiftAdjacentRows.push(lookup); + } + curIndex = data.index > curIndex ? curIndex + 1 : curIndex - 1; + } + } + this.props.selectRowUpdate('cell', data, shiftAdjacentRows); + }; + + handleRowClick = ( + row: MUIDataTableCellValue[], + data: { rowIndex: number; dataIndex: number }, + event: React.MouseEvent, + ) => { + const target = event.target as HTMLElement; + if ( + target.id === 'expandable-button' || + (target.nodeName === 'path' && (target.parentNode as HTMLElement)?.id === 'expandable-button') + ) { + return; + } + + if (target.id && target.id.startsWith('MUIDataTableSelectCell')) return; + + if ( + this.props.options.selectableRowsOnClick && + this.props.options.selectableRows !== 'none' && + this.isRowSelectable(data.dataIndex, this.props.selectedRows) + ) { + const selectRow = { index: data.rowIndex, dataIndex: data.dataIndex }; + this.handleRowSelect(selectRow, event as unknown as React.MouseEvent); + } + + if ( + this.props.options.expandableRowsOnClick && + this.props.options.expandableRows && + this.isRowExpandable(data.dataIndex) + ) { + const expandRow = { index: data.rowIndex, dataIndex: data.dataIndex }; + this.props.toggleExpandRow(expandRow); + } + + if (this.props.options.selectableRowsOnClick) return; + + this.props.options.onRowClick && this.props.options.onRowClick(row, data); + }; + + processRow = (row: MUIDataTableCellValue[], columnOrder: number[]) => { + const ret: { value: MUIDataTableCellValue; index: number }[] = []; + for (let ii = 0; ii < row.length; ii++) { + const columnIndex = columnOrder[ii]; + if (columnIndex === undefined) continue; + ret.push({ value: row[columnIndex], index: columnIndex }); + } + return ret; + }; + + override render() { + const { + classes, + columns, + toggleExpandRow, + options, + columnOrder = this.props.columns.map((_item, idx) => idx), + components = {}, + tableId, + } = this.props; + const tableRows = this.buildRows(); + const visibleColCnt = columns.filter((c) => c.display === 'true').length; + + return ( + + {tableRows && tableRows.length > 0 ? ( + tableRows.map((data, rowIndex) => { + const { data: row, dataIndex } = data; + + if (options.customRowRender) { + return options.customRowRender(row, dataIndex, rowIndex); + } + + const isRowSelected = options.selectableRows !== 'none' ? this.isRowSelected(dataIndex) : false; + const isRowSelectable = this.isRowSelectable(dataIndex); + const bodyClasses = ( + options.setRowProps ? options.setRowProps(row, dataIndex, rowIndex) || {} : {} + ) as Record; + + const processedRow = this.processRow(row, columnOrder); + + return ( + + ) => + this.handleRowClick(row, { rowIndex, dataIndex }, e) + } + className={clsx({ + [classes.lastStackedCell]: + options.responsive === 'vertical' || + options.responsive === 'stacked' || + options.responsive === 'stackedFullWidth', + [classes.lastSimpleCell]: options.responsive === 'simple', + [bodyClasses['className'] as string]: !!bodyClasses['className'], + })} + data-testid={'MUIDataTableBodyRow-' + dataIndex} + id={`MUIDataTableBodyRow-${tableId}-${dataIndex}`}> + + this.handleRowSelect({ index: this.getRowIndex(rowIndex), dataIndex }, e as React.MouseEvent) + } + onExpand={() => toggleExpandRow({ index: this.getRowIndex(rowIndex), dataIndex })} + fixedHeader={options.fixedHeader} + fixedSelectColumn={options.fixedSelectColumn} + checked={isRowSelected} + expandableOn={options.expandableRows} + hideExpandButton={!this.isRowExpandable(dataIndex) && options.expandableRows} + selectableOn={options.selectableRows as string} + selectableRowsHideCheckboxes={options.selectableRowsHideCheckboxes} + isRowExpanded={this.isRowExpanded(dataIndex)} + isRowSelectable={isRowSelectable} + dataIndex={dataIndex} + id={`MUIDataTableSelectCell-${tableId}-${dataIndex}`} + components={components} + /> + {processedRow.map((column) => { + const tableColumn = columns[column.index]; + if (!tableColumn || tableColumn.display !== 'true') return null; + const cellValue = column.value instanceof Date ? String(column.value) : column.value; + + return ( + + {cellValue} + + ); + })} + + {this.isRowExpanded(dataIndex) && + options.renderExpandableRow && + options.renderExpandableRow(row, { rowIndex, dataIndex })} + + ); + }) + ) : ( + + + + {options.textLabels?.body?.noMatch} + + + + )} + + ); + } +} + +export default withStyles(TableBody, defaultBodyStyles, { name: 'MUIDataTableBody' }); diff --git a/src/components/TableBodyCell.js b/src/components/TableBodyCell.tsx similarity index 79% rename from src/components/TableBodyCell.js rename to src/components/TableBodyCell.tsx index 800440bd..058a3fee 100644 --- a/src/components/TableBodyCell.js +++ b/src/components/TableBodyCell.tsx @@ -1,7 +1,8 @@ -import React, { useCallback } from 'react'; +import { useCallback, type MouseEvent, type EventHandler, type SyntheticEvent, type ReactNode } from 'react'; import clsx from 'clsx'; import { TableCell } from '@mui/material'; import { makeStyles } from 'tss-react/mui'; +import type { MUIDataTableOptions } from '../types/options'; const useStyles = makeStyles({ name: 'MUIDataTableBodyCell' })((theme) => ({ root: {}, @@ -90,25 +91,41 @@ const useStyles = makeStyles({ name: 'MUIDataTableBodyCell' })((theme) => ({ }, })); -function TableBodyCell(props) { +interface TableBodyCellProps { + children: ReactNode | ((dataIndex: number, rowIndex: number) => ReactNode); + colIndex: number; + columnHeader: ReactNode; + options: MUIDataTableOptions; + dataIndex: number; + rowIndex: number; + className?: string | undefined; + print: boolean; + tableId?: string | undefined; + [key: string]: unknown; +} + +function TableBodyCell(props: TableBodyCellProps) { const { classes } = useStyles(); const { children, colIndex, columnHeader, options, dataIndex, rowIndex, className, print, tableId, ...otherProps } = props; const onCellClick = options.onCellClick; const handleClick = useCallback( - (event) => { - onCellClick(children, { colIndex, rowIndex, dataIndex, event }); + (event: MouseEvent) => { + if (typeof children !== 'function') { + onCellClick?.(children, { colIndex, rowIndex, dataIndex, event }); + } }, [onCellClick, children, colIndex, rowIndex, dataIndex], ); - // Event listeners. Avoid attaching them if they're not necessary. - let methods = {}; + const methods: Record> = {}; if (onCellClick) { - methods.onClick = handleClick; + methods['onClick'] = handleClick; } + const tableProps = options.setTableProps ? options.setTableProps() : {}; + let cells = [
({ +const defaultBodyRowStyles = (theme: Theme) => ({ root: { - // material v4 '&.Mui-selected': { backgroundColor: theme.palette.action.selected, }, - - // material v3 workaround '&.mui-row-selected': { backgroundColor: theme.palette.action.selected, }, @@ -35,24 +34,33 @@ const defaultBodyRowStyles = (theme) => ({ }, }); -class TableBodyRow extends React.Component { - static propTypes = { - /** Options used to describe table */ - options: PropTypes.object.isRequired, - /** Callback to execute when row is clicked */ - onClick: PropTypes.func, - /** Current row selected or not */ - rowSelected: PropTypes.bool, - /** Extend the style applied to components */ - classes: PropTypes.object, - }; +interface TableBodyRowProps { + options: MUIDataTableOptions; + onClick?: (event: React.MouseEvent) => void; + rowSelected?: boolean; + classes?: Record, string>; + className?: string; + isRowSelectable?: boolean; + children?: ReactNode; + [key: string]: unknown; +} - render() { - const { classes, options, rowSelected, onClick, className, isRowSelectable, ...rest } = this.props; +class TableBodyRow extends React.Component { + override render() { + const { + classes = {} as Record, string>, + options, + rowSelected, + onClick, + className, + isRowSelectable, + ...rest + } = this.props; - var methods = {}; + // ATTENTION + const methods: Record> = {}; if (onClick) { - methods.onClick = onClick; + methods['onClick'] = onClick; } return ( @@ -62,7 +70,6 @@ class TableBodyRow extends React.Component { className={clsx( { [classes.root]: true, - [classes.hover]: options.rowHover, [classes.hoverCursor]: (options.selectableRowsOnClick && isRowSelectable) || options.expandableRowsOnClick, [classes.responsiveSimple]: options.responsive === 'simple', [classes.responsiveStacked]: diff --git a/src/components/TableFilter.js b/src/components/TableFilter.tsx similarity index 50% rename from src/components/TableFilter.js rename to src/components/TableFilter.tsx index 742f5f2e..8301b5da 100644 --- a/src/components/TableFilter.js +++ b/src/components/TableFilter.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { Button, Checkbox, @@ -13,158 +14,178 @@ import { TextField, Typography, } from '@mui/material'; -import PropTypes from 'prop-types'; -import React from 'react'; import clsx from 'clsx'; import { withStyles } from 'tss-react/mui'; import cloneDeep from 'lodash.clonedeep'; +import type { ComponentType } from 'react'; +import type { CSSObject } from 'tss-react'; +import type { MUIDataTableColumnState } from '../types/columns'; +import type { MUIDataTableOptions, MUIDataTableFilterList } from '../types/options'; +import type { Theme } from '@mui/material/styles'; + +export const defaultFilterStyles = (theme: Theme) => + ({ + root: { + backgroundColor: theme.palette.background.default, + padding: '24px 24px 36px 24px', + fontFamily: 'Roboto', + }, + header: { + flex: '0 0 auto', + marginBottom: '16px', + width: '100%', + display: 'flex', + justifyContent: 'space-between', + }, + title: { + display: 'inline-block', + marginLeft: '7px', + color: theme.palette.text.primary, + fontSize: '14px', + fontWeight: 500, + }, + noMargin: { + marginLeft: '0px', + }, + reset: { + alignSelf: 'left', + }, + resetLink: { + marginLeft: '16px', + fontSize: '12px', + cursor: 'pointer', + }, + filtersSelected: { + alignSelf: 'right', + }, + checkboxListTitle: { + marginLeft: '7px', + marginBottom: '8px', + fontSize: '14px', + color: theme.palette.text.secondary, + textAlign: 'left', + fontWeight: 500, + }, + checkboxFormGroup: { + marginTop: '8px', + }, + checkboxFormControl: { + margin: '0px', + }, + checkboxFormControlLabel: { + fontSize: '15px', + marginLeft: '8px', + color: theme.palette.text.primary, + }, + checkboxIcon: { + width: '32px', + height: '32px', + }, + checkbox: {}, + checked: {}, + gridListTile: { + marginTop: '16px', + }, + }) satisfies Record; + +export interface TableFilterProps { + filterData: string[][]; + filterList: MUIDataTableFilterList; + options: MUIDataTableOptions; + columns: MUIDataTableColumnState[]; + onFilterUpdate: ( + index: number, + value: string | string[], + column: string | MUIDataTableColumnState, + type: string, + ) => void; + onFilterReset: () => void; + handleClose: () => void; + updateFilterByType: ( + filterList: MUIDataTableFilterList, + index: number, + value: string | string[], + type: string, + customUpdate: unknown, + ) => void; + classes: Record, string>; + customFooter?: (filterList: MUIDataTableFilterList, applyFilters: () => MUIDataTableFilterList) => React.ReactNode; + components?: { Checkbox?: ComponentType }; +} -export const defaultFilterStyles = (theme) => ({ - root: { - backgroundColor: theme.palette.background.default, - padding: '24px 24px 36px 24px', - fontFamily: 'Roboto', - }, - header: { - flex: '0 0 auto', - marginBottom: '16px', - width: '100%', - display: 'flex', - justifyContent: 'space-between', - }, - title: { - display: 'inline-block', - marginLeft: '7px', - color: theme.palette.text.primary, - fontSize: '14px', - fontWeight: 500, - }, - noMargin: { - marginLeft: '0px', - }, - reset: { - alignSelf: 'left', - }, - resetLink: { - marginLeft: '16px', - fontSize: '12px', - cursor: 'pointer', - }, - filtersSelected: { - alignSelf: 'right', - }, - /* checkbox */ - checkboxListTitle: { - marginLeft: '7px', - marginBottom: '8px', - fontSize: '14px', - color: theme.palette.text.secondary, - textAlign: 'left', - fontWeight: 500, - }, - checkboxFormGroup: { - marginTop: '8px', - }, - checkboxFormControl: { - margin: '0px', - }, - checkboxFormControlLabel: { - fontSize: '15px', - marginLeft: '8px', - color: theme.palette.text.primary, - }, - checkboxIcon: { - width: '32px', - height: '32px', - }, - checkbox: {}, - checked: {}, - gridListTile: { - marginTop: '16px', - }, -}); - -class TableFilter extends React.Component { - static propTypes = { - /** Data used to populate filter dropdown/checkbox */ - filterData: PropTypes.array.isRequired, - /** Data selected to be filtered against dropdown/checkbox */ - filterList: PropTypes.array.isRequired, - /** Options used to describe table */ - options: PropTypes.object.isRequired, - /** Callback to trigger filter update */ - onFilterUpdate: PropTypes.func, - /** Callback to trigger filter reset */ - onFilterReset: PropTypes.func, - /** Extend the style applied to components */ - classes: PropTypes.object, - }; +interface TableFilterState { + filterList: MUIDataTableFilterList; +} - constructor(props) { +class TableFilter extends React.Component { + constructor(props: TableFilterProps) { super(props); this.state = { filterList: cloneDeep(props.filterList), }; } - filterUpdate = (index, value, column, type, customUpdate) => { - let newFilterList = this.state.filterList.slice(0); - + filterUpdate = ( + index: number, + value: string | string[], + column: string | MUIDataTableColumnState, + type: string, + customUpdate?: unknown, + ) => { + const newFilterList = this.state.filterList.slice(0); this.props.updateFilterByType(newFilterList, index, value, type, customUpdate); - this.setState({ - filterList: newFilterList, - }); + this.setState({ filterList: newFilterList }); }; - handleCheckboxChange = (index, value, column) => { + handleCheckboxChange = (index: number, value: string, column: string) => { this.filterUpdate(index, value, column, 'checkbox'); - if (this.props.options.confirmFilters !== true) { this.props.onFilterUpdate(index, value, column, 'checkbox'); } }; - handleDropdownChange = (event, index, column) => { - const labelFilterAll = this.props.options.textLabels.filter.all; - const value = event.target.value === labelFilterAll ? [] : [event.target.value]; + handleDropdownChange = (event: React.ChangeEvent<{ value: unknown }>, index: number, column: string) => { + const labelFilterAll = this.props.options.textLabels?.filter?.all || ''; + const value = event.target.value === labelFilterAll ? [] : [event.target.value as string]; this.filterUpdate(index, value, column, 'dropdown'); - if (this.props.options.confirmFilters !== true) { this.props.onFilterUpdate(index, value, column, 'dropdown'); } }; - handleMultiselectChange = (index, value, column) => { + handleMultiselectChange = (index: number, value: string | string[], column: string) => { this.filterUpdate(index, value, column, 'multiselect'); - if (this.props.options.confirmFilters !== true) { this.props.onFilterUpdate(index, value, column, 'multiselect'); } }; - handleTextFieldChange = (event, index, column) => { + handleTextFieldChange = (event: React.ChangeEvent, index: number, column: string) => { this.filterUpdate(index, event.target.value, column, 'textField'); - if (this.props.options.confirmFilters !== true) { this.props.onFilterUpdate(index, event.target.value, column, 'textField'); } }; - handleCustomChange = (value, index, column) => { - this.filterUpdate(index, value, column.name, column.filterType); - + handleCustomChange = (value: string | string[], index: number, column: MUIDataTableColumnState) => { + this.filterUpdate(index, value, column.name, column.filterType as string); if (this.props.options.confirmFilters !== true) { - this.props.onFilterUpdate(index, value, column.name, column.filterType); + this.props.onFilterUpdate(index, value, column.name, column.filterType as string); } }; - renderCheckbox(column, index, components = {}) { + renderCheckbox( + column: MUIDataTableColumnState, + index: number, + components: { Checkbox?: ComponentType } = {}, + ) { const CheckboxComponent = components.Checkbox || Checkbox; - const { classes, filterData } = this.props; const { filterList } = this.state; - const renderItem = - column.filterOptions && column.filterOptions.renderValue ? column.filterOptions.renderValue : (v) => v; + const filterOpts = column.filterOptions as Record | undefined; + const renderItem = filterOpts?.['renderValue'] + ? (filterOpts['renderValue'] as (v: string) => string) + : (v: string) => v; return ( @@ -175,7 +196,7 @@ class TableFilter extends React.Component { - {filterData[index].map((filterValue, filterIndex) => ( + {(filterData[index] ?? []).map((filterValue, filterIndex) => ( = 0} - classes={{ - root: classes.checkbox, - checked: classes.checked, - }} + checked={(filterList[index] ?? []).indexOf(filterValue) >= 0} + classes={{ root: classes.checkbox, checked: classes.checked }} value={filterValue != null ? filterValue.toString() : ''} /> } @@ -207,15 +225,15 @@ class TableFilter extends React.Component { ); } - renderSelect(column, index) { + renderSelect(column: MUIDataTableColumnState, index: number) { const { classes, filterData, options } = this.props; const { filterList } = this.state; - const textLabels = options.textLabels.filter; - const renderItem = - column.filterOptions && column.filterOptions.renderValue - ? column.filterOptions.renderValue - : (v) => (v != null ? v.toString() : ''); - const width = (column.filterOptions && column.filterOptions.fullWidth) === true ? 12 : 6; + const textLabels = options.textLabels?.filter; + const filterOpts = column.filterOptions as Record | undefined; + const renderItem = filterOpts?.['renderValue'] + ? (filterOpts['renderValue'] as (v: string) => string) + : (v: string) => (v != null ? v.toString() : ''); + const width = filterOpts?.['fullWidth'] === true ? 12 : 6; return ( @@ -223,15 +241,17 @@ class TableFilter extends React.Component { {column.label} }> - - {textLabels.all} + + {textLabels?.all} - {filterData[index].map((filterValue, filterIndex) => ( + {filterData[index]?.map((filterValue, filterIndex) => ( {renderItem(filterValue)} @@ -242,13 +262,14 @@ class TableFilter extends React.Component { ); } - renderTextField(column, index) { + renderTextField(column: MUIDataTableColumnState, index: number) { const { classes } = this.props; const { filterList } = this.state; - if (column.filterOptions && column.filterOptions.renderValue) { + const filterOpts = column.filterOptions as Record | undefined; + if (filterOpts?.['renderValue']) { console.warn('Custom renderValue not supported for textField filters'); } - const width = (column.filterOptions && column.filterOptions.fullWidth) === true ? 12 : 6; + const width = filterOpts?.['fullWidth'] === true ? 12 : 6; return ( @@ -257,23 +278,29 @@ class TableFilter extends React.Component { fullWidth variant={'standard'} label={column.label} - value={filterList[index].toString() || ''} + value={filterList[index]?.toString() || ''} data-testid={'filtertextfield-' + column.name} - onChange={(event) => this.handleTextFieldChange(event, index, column.name)} + onChange={(event) => + this.handleTextFieldChange(event as React.ChangeEvent, index, column.name) + } /> ); } - renderMultiselect(column, index, components = {}) { + renderMultiselect( + column: MUIDataTableColumnState, + index: number, + components: { Checkbox?: ComponentType } = {}, + ) { const CheckboxComponent = components.Checkbox || Checkbox; - const { classes, filterData } = this.props; const { filterList } = this.state; - const renderItem = - column.filterOptions && column.filterOptions.renderValue ? column.filterOptions.renderValue : (v) => v; - const width = (column.filterOptions && column.filterOptions.fullWidth) === true ? 12 : 6; + const filterOpts = column.filterOptions; + const renderItem = filterOpts?.['renderValue'] ? filterOpts['renderValue'] : (v: string) => v; + const width = filterOpts?.['fullWidth'] === true ? 12 : 6; + return ( @@ -282,23 +309,20 @@ class TableFilter extends React.Component { multiple fullWidth value={filterList[index] || []} - renderValue={(selected) => selected.map(renderItem).join(', ')} + renderValue={(selected) => (selected as string[]).map(renderItem).join(', ')} name={column.name} - onChange={(event) => this.handleMultiselectChange(index, event.target.value, column.name)} + onChange={(event) => this.handleMultiselectChange(index, event.target.value as string[], column.name)} MenuProps={{ disablePortal: true }} input={}> - {filterData[index].map((filterValue, filterIndex) => ( + {filterData[index]?.map((filterValue, filterIndex) => ( = 0} + checked={(filterData[index] ?? []).indexOf(filterValue) >= 0} value={filterValue != null ? filterValue.toString() : ''} className={classes.checkboxIcon} - classes={{ - root: classes.checkbox, - checked: classes.checked, - }} + classes={{ root: classes.checkbox, checked: classes.checked }} /> @@ -309,82 +333,79 @@ class TableFilter extends React.Component { ); } - renderCustomField(column, index) { + renderCustomField(column: MUIDataTableColumnState, index: number) { const { classes, filterData, options } = this.props; const { filterList } = this.state; - const width = (column.filterOptions && column.filterOptions.fullWidth) === true ? 12 : 6; + const filterOpts = column.filterOptions as Record | undefined; + const width = filterOpts?.['fullWidth'] === true ? 12 : 6; const display = - (column.filterOptions && column.filterOptions.display) || - (options.filterOptions && options.filterOptions.display); + filterOpts?.['display'] || + (options.filterOptions && (options.filterOptions as Record)['display']); if (!display) { console.error('Property "display" is required when using custom filter type.'); return; } - if (column.filterListOptions && column.filterListOptions.renderValue) { - console.warning('"renderValue" is ignored for custom filter fields'); - } return ( - {display(filterList, this.handleCustomChange, index, column, filterData)} + {( + display as ( + filterList: MUIDataTableFilterList, + onChange: unknown, + index: number, + column: MUIDataTableColumnState, + filterData: string[][], + ) => React.ReactNode + )(filterList, this.handleCustomChange, index, column, filterData)} ); } applyFilters = () => { - this.state.filterList.forEach((filter, index) => { - this.props.onFilterUpdate(index, filter, this.props.columns[index], 'custom'); - }); - - this.props.handleClose(); // close filter dialog popover - + this.state.filterList.forEach((filter, index) => + this.props.onFilterUpdate(index, filter, this.props.columns[index]!, 'custom'), + ); + this.props.handleClose(); if (this.props.options.onFilterConfirm) { this.props.options.onFilterConfirm(this.state.filterList); } - return this.state.filterList; }; resetFilters = () => { - this.setState({ - filterList: this.props.columns.map(() => []), - }); + this.setState({ filterList: this.props.columns.map(() => []) }); if (this.props.options.confirmFilters !== true) { this.props.onFilterReset(); } }; - render() { + override render() { const { classes, columns, options, customFooter, filterList, components = {} } = this.props; - const textLabels = options.textLabels.filter; + const textLabels = options.textLabels?.filter; return (
- - {textLabels.title} + + {textLabels?.title}
- + {columns.map((column, index) => { if (column.filter) { const filterType = column.filterType || options.filterType; @@ -398,6 +419,7 @@ class TableFilter extends React.Component { ? this.renderCustomField(column, index) : this.renderSelect(column, index); } + return null; })} {customFooter ? customFooter(filterList, this.applyFilters) : ''} diff --git a/src/components/TableFilterList.js b/src/components/TableFilterList.js deleted file mode 100644 index 3c7ecd9a..00000000 --- a/src/components/TableFilterList.js +++ /dev/null @@ -1,136 +0,0 @@ -import { makeStyles } from 'tss-react/mui'; -import PropTypes from 'prop-types'; -import React from 'react'; -import TableFilterListItem from './TableFilterListItem'; - -const useStyles = makeStyles({ name: 'MUIDataTableFilterList' })(() => ({ - root: { - display: 'flex', - justifyContent: 'left', - flexWrap: 'wrap', - margin: '0px 16px 0px 16px', - }, - chip: { - margin: '8px 8px 0px 0px', - }, -})); - -const TableFilterList = ({ - options, - filterList, - filterUpdate, - filterListRenderers, - columnNames, - serverSideFilterList, - customFilterListUpdate, - ItemComponent = TableFilterListItem, -}) => { - const { classes } = useStyles(); - const { serverSide } = options; - - const removeFilter = (index, filterValue, columnName, filterType, customFilterListUpdate = null) => { - let removedFilter = filterValue; - if (Array.isArray(removedFilter) && removedFilter.length === 0) { - removedFilter = filterList[index]; - } - - filterUpdate(index, filterValue, columnName, filterType, customFilterListUpdate, (filterList) => { - if (options.onFilterChipClose) { - options.onFilterChipClose(index, removedFilter, filterList); - } - }); - }; - const customFilterChip = (customFilterItem, index, customFilterItemIndex, item, isArray) => { - let type; - // If our custom filter list is an array, we need to check for custom update functions to determine - // default type. Otherwise we use the supplied type in options. - if (isArray) { - type = customFilterListUpdate[index] ? 'custom' : 'chip'; - } else { - type = columnNames[index].filterType; - } - - return ( - - removeFilter( - index, - item[customFilterItemIndex] || [], - columnNames[index].name, - type, - customFilterListUpdate[index], - ) - } - className={classes.chip} - itemKey={customFilterItemIndex} - index={index} - data={item} - columnNames={columnNames} - filterProps={ - options.setFilterChipProps - ? options.setFilterChipProps(index, columnNames[index].name, item[customFilterItemIndex] || []) - : {} - } - /> - ); - }; - - const filterChip = (index, data, colIndex) => ( - removeFilter(index, data, columnNames[index].name, 'chip')} - className={classes.chip} - itemKey={colIndex} - index={index} - data={data} - columnNames={columnNames} - filterProps={options.setFilterChipProps ? options.setFilterChipProps(index, columnNames[index].name, data) : {}} - /> - ); - - const getFilterList = (filterList) => { - return filterList.map((item, index) => { - if (columnNames[index].filterType === 'custom' && filterList[index].length) { - const filterListRenderersValue = filterListRenderers[index](item); - - if (Array.isArray(filterListRenderersValue)) { - return filterListRenderersValue.map((customFilterItem, customFilterItemIndex) => - customFilterChip(customFilterItem, index, customFilterItemIndex, item, true), - ); - } else { - return customFilterChip(filterListRenderersValue, index, index, item, false); - } - } - - return item.map((data, colIndex) => filterChip(index, data, colIndex)); - }); - }; - - return ( -
- {serverSide && serverSideFilterList ? getFilterList(serverSideFilterList) : getFilterList(filterList)} -
- ); -}; - -TableFilterList.propTypes = { - /** Data used to filter table against */ - filterList: PropTypes.array.isRequired, - /** Filter List value renderers */ - filterListRenderers: PropTypes.array.isRequired, - /** Columns used to describe table */ - columnNames: PropTypes.arrayOf( - PropTypes.oneOfType([ - PropTypes.string, - PropTypes.shape({ name: PropTypes.string.isRequired, filterType: PropTypes.string }), - ]), - ).isRequired, - /** Callback to trigger filter update */ - onFilterUpdate: PropTypes.func, - ItemComponent: PropTypes.any, -}; - -export default TableFilterList; diff --git a/src/components/TableFilterList.tsx b/src/components/TableFilterList.tsx new file mode 100644 index 00000000..c11781c6 --- /dev/null +++ b/src/components/TableFilterList.tsx @@ -0,0 +1,158 @@ +import React from 'react'; +import { makeStyles } from 'tss-react/mui'; +import TableFilterListItem from './TableFilterListItem'; +import type { ComponentType } from 'react'; +import type { MUIDataTableOptions, MUIDataTableFilterList } from '../types/options'; +import type { TableFilterListItemProps } from '../types/components'; + +const useStyles = makeStyles({ name: 'MUIDataTableFilterList' })(() => ({ + root: { + display: 'flex', + justifyContent: 'left', + flexWrap: 'wrap', + margin: '0px 16px 0px 16px', + }, + chip: { + margin: '8px 8px 0px 0px', + }, +})); + +interface ColumnName { + name: string; + filterType: string; +} + +export interface TableFilterListProps { + options: MUIDataTableOptions; + filterList: MUIDataTableFilterList; + filterUpdate: ( + index: number, + value: string | string[], + columnName: string, + filterType: string, + customUpdate: + | ((filterList: MUIDataTableFilterList, filterPos: number, index: number) => MUIDataTableFilterList) + | null, + callback: (filterList: MUIDataTableFilterList) => void, + ) => void; + filterListRenderers: ((value: string | string[]) => string | string[])[]; + columnNames: ColumnName[]; + serverSideFilterList?: MUIDataTableFilterList | null; + customFilterListUpdate: ( + | ((filterList: MUIDataTableFilterList, filterPos: number, index: number) => MUIDataTableFilterList) + | null + )[]; + ItemComponent?: ComponentType; +} + +const TableFilterList = ({ + options, + filterList, + filterUpdate, + filterListRenderers, + columnNames, + serverSideFilterList, + customFilterListUpdate, + ItemComponent = TableFilterListItem, +}: TableFilterListProps) => { + const { classes } = useStyles(); + const { serverSide } = options; + const removeFilter = ( + index: number, + filterValue: string | string[], + columnName: string, + filterType: string, + customUpdate: + | ((filterList: MUIDataTableFilterList, filterPos: number, idx: number) => MUIDataTableFilterList) + | null = null, + ) => { + let removedFilter: string | string[] = filterValue; + if (Array.isArray(removedFilter) && removedFilter.length === 0) { + removedFilter = filterList[index] as string | string[]; + } + + filterUpdate(index, filterValue, columnName, filterType, customUpdate, (fl) => { + if (options.onFilterChipClose) { + options.onFilterChipClose(index, removedFilter as string, fl); + } + }); + }; + + const customFilterChip = ( + customFilterItem: string, + index: number, + customFilterItemIndex: number, + item: string[], + isArray: boolean, + ) => { + const type = isArray ? (customFilterListUpdate[index] ? 'custom' : 'chip') : columnNames[index]?.filterType; + + return ( + + removeFilter( + index, + item[customFilterItemIndex] || [], + columnNames[index]?.name as string, + type as string, + customFilterListUpdate[index], + ) + } + className={classes.chip} + itemKey={customFilterItemIndex} + index={index} + data={item} + columnNames={columnNames} + filterProps={ + options.setFilterChipProps + ? options.setFilterChipProps(index, columnNames[index]?.name as string, item[customFilterItemIndex] || '') + : {} + } + /> + ); + }; + + const filterChip = (index: number, data: string, colIndex: number) => ( + removeFilter(index, data, columnNames[index]?.name as string, 'chip')} + className={classes.chip} + itemKey={colIndex} + index={index} + data={data} + columnNames={columnNames} + filterProps={ + options.setFilterChipProps ? options.setFilterChipProps(index, columnNames[index]?.name as string, data) : {} + } + /> + ); + + const getFilterList = (fl: MUIDataTableFilterList) => { + return fl.map((item, index) => { + if (columnNames[index]?.filterType === 'custom' && fl[index]?.length) { + const filterListRenderersValue = filterListRenderers[index]?.(item); + + if (Array.isArray(filterListRenderersValue)) { + return filterListRenderersValue.map((customFilterItem, customFilterItemIndex) => + customFilterChip(customFilterItem, index, customFilterItemIndex, item, true), + ); + } else { + return customFilterChip(filterListRenderersValue as string, index, index, item, false); + } + } + + return item.map((data, colIndex) => filterChip(index, data, colIndex)); + }); + }; + + return ( +
+ {serverSide && serverSideFilterList ? getFilterList(serverSideFilterList) : getFilterList(filterList)} +
+ ); +}; + +export default TableFilterList; diff --git a/src/components/TableFilterListItem.js b/src/components/TableFilterListItem.js deleted file mode 100644 index 7531cbf6..00000000 --- a/src/components/TableFilterListItem.js +++ /dev/null @@ -1,20 +0,0 @@ -import { Chip } from '@mui/material'; -import PropTypes from 'prop-types'; -import React from 'react'; -import clsx from 'clsx'; - -const TableFilterListItem = ({ label, onDelete, className, filterProps }) => { - filterProps = filterProps || {}; - if (filterProps.className) { - className = clsx(className, filterProps.className); - } - return ; -}; - -TableFilterListItem.propTypes = { - label: PropTypes.node, - onDelete: PropTypes.func.isRequired, - className: PropTypes.string.isRequired, -}; - -export default TableFilterListItem; diff --git a/src/components/TableFilterListItem.tsx b/src/components/TableFilterListItem.tsx new file mode 100644 index 00000000..9cfa948e --- /dev/null +++ b/src/components/TableFilterListItem.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Chip } from '@mui/material'; +import clsx from 'clsx'; +import type { ComponentProps } from 'react'; +import type { TableFilterListItemProps } from '../types/components'; + +const TableFilterListItem = ({ label, onDelete, className, filterProps }: TableFilterListItemProps) => { + const mergedProps = (filterProps || {}) as ComponentProps; + let mergedClassName = className; + if (typeof mergedProps.className === 'string') { + mergedClassName = clsx(className, mergedProps.className); + } + return ; +}; + +export default TableFilterListItem; diff --git a/src/components/TableFooter.js b/src/components/TableFooter.tsx similarity index 55% rename from src/components/TableFooter.js rename to src/components/TableFooter.tsx index f9ca2314..48ceafed 100644 --- a/src/components/TableFooter.js +++ b/src/components/TableFooter.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Table as MuiTable } from '@mui/material'; import TablePagination from './TablePagination'; import { makeStyles } from 'tss-react/mui'; -import PropTypes from 'prop-types'; +import type { MUIDataTableOptions } from '../types/options'; const useStyles = makeStyles({ name: 'MUIDataTableFooter' })(() => ({ root: { @@ -12,20 +12,29 @@ const useStyles = makeStyles({ name: 'MUIDataTableFooter' })(() => ({ }, })); -const TableFooter = ({ options, rowCount, page, rowsPerPage, changeRowsPerPage, changePage }) => { +export interface TableFooterProps { + options: MUIDataTableOptions; + rowCount: number; + page: number; + rowsPerPage: number; + changeRowsPerPage: (rowsPerPage: number) => void; + changePage: (page: number) => void; +} + +const TableFooter = ({ options, rowCount, page, rowsPerPage, changeRowsPerPage, changePage }: TableFooterProps) => { const { classes } = useStyles(); const { customFooter, pagination = true } = options; - if (customFooter) { + if (customFooter && typeof customFooter === 'function') { return ( - {options.customFooter( + {customFooter( rowCount, page, rowsPerPage, changeRowsPerPage, changePage, - options.textLabels.pagination, + options.textLabels?.pagination as never, )} ); @@ -40,7 +49,6 @@ const TableFooter = ({ options, rowCount, page, rowsPerPage, changeRowsPerPage, rowsPerPage={rowsPerPage} changeRowsPerPage={changeRowsPerPage} changePage={changePage} - component={'div'} options={options} /> @@ -50,25 +58,4 @@ const TableFooter = ({ options, rowCount, page, rowsPerPage, changeRowsPerPage, return null; }; -TableFooter.propTypes = { - /** Total number of table rows */ - rowCount: PropTypes.number.isRequired, - /** Options used to describe table */ - options: PropTypes.shape({ - customFooter: PropTypes.func, - pagination: PropTypes.bool, - textLabels: PropTypes.shape({ - pagination: PropTypes.object, - }), - }), - /** Current page index */ - page: PropTypes.number.isRequired, - /** Total number allowed of rows per page */ - rowsPerPage: PropTypes.number.isRequired, - /** Callback to trigger rows per page change */ - changeRowsPerPage: PropTypes.func.isRequired, - /** Callback to trigger page change */ - changePage: PropTypes.func.isRequired, -}; - export default TableFooter; diff --git a/src/components/TableHead.js b/src/components/TableHead.tsx similarity index 67% rename from src/components/TableHead.js rename to src/components/TableHead.tsx index cf6062bf..21f2ef4f 100644 --- a/src/components/TableHead.js +++ b/src/components/TableHead.tsx @@ -1,10 +1,20 @@ +import React, { useState } from 'react'; import { makeStyles } from 'tss-react/mui'; import clsx from 'clsx'; import { TableHead as MuiTableHead } from '@mui/material'; -import React, { useState } from 'react'; import TableHeadCell from './TableHeadCell'; import TableHeadRow from './TableHeadRow'; import TableSelectCell from './TableSelectCell'; +import type { ComponentType } from 'react'; +import type { MUIDataTableColumnState } from '../types/columns'; +import type { + MUIDataTableOptions, + MUIDataTableSelectedRows, + MUIDataTableExpandedRows, + MUIDataTableDisplayRow, + MUIDataTableSortOrder, +} from '../types/options'; +import type { HeadCellRefs, ColumnDropTimers } from '../types/drag'; const useStyles = makeStyles({ name: 'MUIDataTableHead' })((theme) => ({ main: {}, @@ -23,6 +33,27 @@ const useStyles = makeStyles({ name: 'MUIDataTableHead' })((theme) => ({ }, })); +export interface TableHeadProps { + columnOrder?: number[] | null; + columns: MUIDataTableColumnState[]; + components?: { Tooltip?: ComponentType; Checkbox?: ComponentType }; + count: number; + data: MUIDataTableDisplayRow[]; + draggableHeadCellRefs?: HeadCellRefs; + expandedRows?: MUIDataTableExpandedRows; + options: MUIDataTableOptions; + selectedRows: MUIDataTableSelectedRows; + selectRowUpdate: (type: string, val: null) => void; + setCellRef?: (index: number, pos: number, el: HTMLTableCellElement | null) => void; + sortOrder?: Partial; + tableRef?: () => HTMLElement | null; + tableId?: string; + timers?: ColumnDropTimers; + toggleAllExpandableRows?: () => void; + toggleSort: (index: number) => void; + updateColumnOrder?: (columnOrder: number[], src: number, target: number) => void; +} + const TableHead = ({ columnOrder = null, columns, @@ -42,16 +73,17 @@ const TableHead = ({ toggleAllExpandableRows, toggleSort, updateColumnOrder, -}) => { +}: TableHeadProps) => { const { classes } = useStyles(); - if (columnOrder === null) { - columnOrder = columns ? columns.map((item, idx) => idx) : []; + let resolvedColumnOrder = columnOrder; + if (resolvedColumnOrder === null) { + resolvedColumnOrder = columns ? columns.map((_item, idx) => idx) : []; } const [dragging, setDragging] = useState(false); - const handleToggleColumn = (index) => { + const handleToggleColumn = (index: number) => { toggleSort(index); }; @@ -73,7 +105,7 @@ const TableHead = ({ ) { if (isChecked) { for (let ii = 0; ii < data.length; ii++) { - if (!selectedRows.lookup[data[ii].dataIndex]) { + if (!selectedRows.lookup[data[ii]?.dataIndex as number]) { isChecked = false; isIndeterminate = true; break; @@ -86,13 +118,11 @@ const TableHead = ({ } } - let orderedColumns = columnOrder.map((colIndex, idx) => { - return { - column: columns[colIndex], - index: colIndex, - colPos: idx, - }; - }); + const orderedColumns = resolvedColumnOrder.map((colIndex, idx) => ({ + column: columns[colIndex], + index: colIndex, + colPos: idx, + })); return ( {orderedColumns.map( ({ column, index, colPos }) => - column.display === 'true' && + column?.display === 'true' && (column.customHeadRender ? ( - - {column.customHeadRender({ index, ...column }, handleToggleColumn, sortOrder)} - + column.customHeadRender({ index, ...column }, handleToggleColumn, sortOrder as MUIDataTableSortOrder) ) : ( ({ root: {}, @@ -60,6 +63,30 @@ const useStyles = makeStyles({ name: 'MUIDataTableHeadCell' })((theme) => ({ }, })); +interface TableHeadCellProps { + cellHeaderProps?: ComponentProps; + children?: ReactNode; + colPosition?: number; + column?: MUIDataTableColumnState; + columns?: MUIDataTableColumnState[]; + columnOrder?: number[]; + components?: { Tooltip?: ComponentType }; + draggableHeadCellRefs?: HeadCellRefs | undefined; + draggingHook?: [boolean, (val: boolean) => void]; + hint?: string | undefined; + index: number; + options: MUIDataTableOptions; + print: boolean; + setCellRef?: ((index: number, pos: number, el: HTMLTableCellElement | null) => void) | undefined; + sort: boolean; + sortDirection?: MUIDataTableSortDirection | undefined; + tableRef?: (() => HTMLElement | null) | undefined; + tableId?: string | undefined; + timers?: ColumnDropTimers | undefined; + toggleSort: (index: number) => void; + updateColumnOrder?: ((columnOrder: number[], src: number, target: number) => void) | undefined; +} + const TableHeadCell = ({ cellHeaderProps = {}, children, @@ -82,13 +109,13 @@ const TableHeadCell = ({ timers, toggleSort, updateColumnOrder, -}) => { +}: TableHeadCellProps) => { const [sortTooltipOpen, setSortTooltipOpen] = useState(false); const [hintTooltipOpen, setHintTooltipOpen] = useState(false); const { classes } = useStyles(); - const handleKeyboardSortInput = (e) => { + const handleKeyboardSortInput = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { toggleSort(index); } @@ -100,16 +127,32 @@ const TableHeadCell = ({ toggleSort(index); }; - const [dragging, setDragging] = draggingHook ? draggingHook : []; + const [dragging, setDragging] = draggingHook ? draggingHook : ([] as unknown as [boolean, (val: boolean) => void]); - const { className, ...otherProps } = cellHeaderProps; - const Tooltip = components.Tooltip || MuiTooltip; + const { className, ...otherProps } = cellHeaderProps as { className?: string; [key: string]: unknown }; + const Tooltip = (components.Tooltip || MuiTooltip) as typeof MuiTooltip; const sortActive = sortDirection !== 'none' && sortDirection !== undefined; const ariaSortDirection = sortDirection === 'none' ? false : sortDirection; const isDraggingEnabled = () => { if (!draggingHook) return false; - return options.draggableColumns && options.draggableColumns.enabled && column.draggable !== false; + return ( + options.draggableColumns && + options.draggableColumns.enabled && + (column as unknown as Record)?.['draggable'] !== false + ); + }; + + const setDragRefButton: RefCallback = (node) => { + if (isDraggingEnabled()) { + dragRef(node); + } + }; + + const setDragRefDiv: RefCallback = (node) => { + if (isDraggingEnabled()) { + dragRef(node); + } }; const sortLabelProps = { @@ -117,24 +160,24 @@ const TableHeadCell = ({ tabIndex: -1, active: sortActive, hideSortIcon: true, - ...(ariaSortDirection ? { direction: sortDirection } : {}), + ...(ariaSortDirection ? { direction: sortDirection as 'asc' | 'desc' } : {}), }; - const [{ opacity }, dragRef, preview] = useDrag({ + const [{ opacity }, dragRef] = useDrag({ type: 'HEADER', item: () => { setTimeout(() => { setHintTooltipOpen(false); setSortTooltipOpen(false); - setDragging(true); + if (setDragging) setDragging(true); }, 0); return { colIndex: index, headCellRefs: draggableHeadCellRefs, }; }, - end: (item, monitor) => { - setDragging(false); + end: () => { + if (setDragging) setDragging(false); }, collect: (monitor) => { return { @@ -144,40 +187,37 @@ const TableHeadCell = ({ }); const [drop] = useColumnDrop({ - drop: (item, mon) => { + drop: () => { setSortTooltipOpen(false); setHintTooltipOpen(false); - setDragging(false); + if (setDragging) setDragging(false); }, index, - headCellRefs: draggableHeadCellRefs, - updateColumnOrder, + headCellRefs: draggableHeadCellRefs || {}, + updateColumnOrder: updateColumnOrder || (() => {}), columnOrder, - columns, - transitionTime: options.draggableColumns ? options.draggableColumns.transitionTime : 300, + columns: columns || [], + transitionTime: options.draggableColumns ? options.draggableColumns.transitionTime! : 300, tableRef: tableRef ? tableRef() : null, tableId: tableId || 'none', - timers, + timers: timers || {}, }); const cellClass = clsx({ [classes.root]: true, [classes.fixedHeader]: options.fixedHeader, 'datatables-noprint': !print, - [className]: className, + [className as string]: !!className, }); - - const showHintTooltip = () => { - setSortTooltipOpen(false); - setHintTooltipOpen(true); - }; + // ?? const getTooltipTitle = () => { if (dragging) return ''; if (!options.textLabels) return ''; - return options.textLabels.body.columnHeaderTooltip - ? options.textLabels.body.columnHeaderTooltip(column) - : options.textLabels.body.toolTip; + if (column && options.textLabels.body?.columnHeaderTooltip) { + return (options.textLabels.body as { columnHeaderTooltip: (col: unknown) => string }).columnHeaderTooltip(column); + } + return options.textLabels.body?.toolTip || ''; }; const closeTooltip = () => { @@ -186,13 +226,13 @@ const TableHeadCell = ({ return ( { + ref={(ref: HTMLTableCellElement | null) => { drop && drop(ref); - setCellRef && setCellRef(index + 1, colPosition + 1, ref); + setCellRef && setCellRef(index + 1, (colPosition || 0) + 1, ref); }} className={cellClass} scope={'col'} - sortDirection={ariaSortDirection} + sortDirection={ariaSortDirection || undefined} data-colindex={index} data-tableid={tableId} onMouseDown={closeTooltip} @@ -210,12 +250,12 @@ const TableHeadCell = ({ popper: classes.mypopper, }}>