From 9728471eaa14e397299cc4c007d1761b9e27bb69 Mon Sep 17 00:00:00 2001 From: Vlad Rindevich Date: Mon, 30 Mar 2026 23:59:05 +0000 Subject: [PATCH 1/5] refactor: update deps & code to 2026 toolset --- .babelrc.js | 60 - .devcontainer/devcontainer.json | 40 + .devcontainer/post-create.sh | 4 + .eslintignore | 3 - .eslintrc | 19 - .gitignore | 5 + .lintstagedrc | 4 - .lintstagedrc.json | 4 + .npmrc | 3 + .oxfmtrc.json | 7 + .oxlintrc.json | 591 + .prettierrc | 7 - .prettierrc.json | 6 + .storybook/addons.js | 2 - .storybook/config.js | 11 - .storybook/main.ts | 9 + .storybook/preview.tsx | 24 + .storybook/webpack.config.js | 12 - __stories__/AsyncData.story.tsx | 85 +- __stories__/AsyncDataIdle.story.tsx | 99 +- __stories__/BigData.story.tsx | 87 +- __stories__/FixedSizeTree.story.tsx | 71 +- __stories__/MultipleRoots.story.tsx | 92 +- __stories__/VariableSizeTree.story.tsx | 84 +- __stories__/index.ts | 6 - __stories__/utils.ts | 30 +- __tests__/FixedSizeTree.spec.tsx | 820 +- __tests__/VariableSizeTree.spec.tsx | 794 +- __tests__/multiroot.spec.tsx | 227 +- __tests__/setup.ts | 87 + __tests__/utils/misc.ts | 297 +- config/enzyme.js | 5 - eslint.config.ts | 50 + jest.config.js | 10 - package-lock.json | 35922 ++++++----------------- package.json | 131 +- sonar-project.properties | 6 - src/FixedSizeTree.tsx | 37 +- src/Tree.tsx | 190 +- src/VariableSizeTree.tsx | 58 +- src/utils.ts | 35 +- tsconfig.json | 56 +- tsconfig.prod.json | 9 - tsdown.config.ts | 16 + vitest.config.ts | 15 + 45 files changed, 12005 insertions(+), 28125 deletions(-) delete mode 100644 .babelrc.js create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/post-create.sh delete mode 100644 .eslintignore delete mode 100644 .eslintrc delete mode 100644 .lintstagedrc create mode 100644 .lintstagedrc.json create mode 100644 .npmrc create mode 100644 .oxfmtrc.json create mode 100644 .oxlintrc.json delete mode 100644 .prettierrc create mode 100644 .prettierrc.json delete mode 100644 .storybook/addons.js delete mode 100644 .storybook/config.js create mode 100644 .storybook/main.ts create mode 100644 .storybook/preview.tsx delete mode 100644 .storybook/webpack.config.js delete mode 100644 __stories__/index.ts create mode 100644 __tests__/setup.ts delete mode 100644 config/enzyme.js create mode 100644 eslint.config.ts delete mode 100644 jest.config.js delete mode 100644 sonar-project.properties delete mode 100644 tsconfig.prod.json create mode 100644 tsdown.config.ts create mode 100644 vitest.config.ts diff --git a/.babelrc.js b/.babelrc.js deleted file mode 100644 index acc4d23..0000000 --- a/.babelrc.js +++ /dev/null @@ -1,60 +0,0 @@ -const {BUILD_TYPE} = process.env; -const isCjs = BUILD_TYPE === 'cjs'; -const isLib = BUILD_TYPE === 'lib'; - -module.exports = (api) => ({ - plugins: [ - [require('@babel/plugin-proposal-class-properties'), {loose: true}], - [require('@babel/plugin-proposal-optional-chaining'), {loose: true}], - [ - require('@babel/plugin-proposal-nullish-coalescing-operator'), - {loose: true}, - ], - ...(isLib - ? [] - : [ - [ - require('@babel/plugin-transform-runtime'), - { - regenerator: false, - }, - ], - [require('@babel/plugin-proposal-object-rest-spread'), {loose: true}], - ]), - ], - presets: [ - require('@babel/preset-typescript'), - [require('@babel/preset-react'), {useBuiltIns: true}], - ...(isLib - ? [] - : [ - [ - require('@babel/preset-env'), - { - loose: true, - modules: isCjs ? 'commonjs' : false, - shippedProposals: true, - targets: { - browsers: ['last 2 versions', 'IE 11'], - }, - useBuiltIns: false, - }, - ], - ]), - ], - ...(api.env('test') && { - presets: [ - require('@babel/preset-typescript'), - [require('@babel/preset-react'), {useBuiltIns: true}], - [ - require('@babel/preset-env'), - { - modules: 'commonjs', - targets: { - node: process.versions.node, - }, - }, - ], - ], - }), -}); diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..69ffb28 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,40 @@ +{ + "name": "react-vtree", + "image": "ghcr.io/ausginer/devimages/node:latest", + "workspaceFolder": "/workspaces/react-vtree", + "runArgs": ["--name=reactvtree_dev"], + "containerEnv": { + "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD": "1", + "CODEX_HOME": "/workspaces/react-vtree/.agents/codex" + }, + "forwardPorts": [9876, 6006, 5176, 24678], + "remoteUser": "node", + "updateRemoteUserUID": true, + "mounts": [ + { + "source": "${localWorkspaceFolderBasename}_nodemodules", + "target": "${containerWorkspaceFolder}/node_modules", + "type": "volume" + } + ], + "customizations": { + "vscode": { + "extensions": [ + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "EditorConfig.EditorConfig", + "runem.lit-plugin", + "openai.chatgpt", + "vitest.explorer", + "github.vscode-github-actions", + "oxc.oxc-vscode", + "YoavBls.pretty-ts-errors", + "acoreyj.restart-ts-eslint-server", + "TypeScriptTeam.native-preview", + "eamodio.gitlens", + "unifiedjs.vscode-mdx" + ] + } + }, + "postCreateCommand": "bash .devcontainer/post-create.sh" +} diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100644 index 0000000..285c153 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +sudo chown -R node:node /workspaces/react-vtree/node_modules diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 3d22444..0000000 --- a/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -**/node_modules/**/* -**/dist/**/* -docs diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index fee67f7..0000000 --- a/.eslintrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": [ - "eslint-config-poetez/typescript-react", - "eslint-config-prettier/@typescript-eslint", - "prettier/react", - "plugin:prettier/recommended" - ], - "env": { - "browser": true, - "node": true, - "es6": true, - "jest": true - }, - "rules": { - "@typescript-eslint/promise-function-async": "off", - "@typescript-eslint/prefer-for-of": "off", - "guard-for-in": "off" - } -} diff --git a/.gitignore b/.gitignore index efd7a3b..802265a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,10 +6,13 @@ # testing /.nyc_output /coverage +/storybook-static # production /dist /.cache +/.vite +/.vitest # misc .DS_Store @@ -17,3 +20,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +.agents/codex diff --git a/.lintstagedrc b/.lintstagedrc deleted file mode 100644 index b00336f..0000000 --- a/.lintstagedrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "+(.storybook|config)/*.js": ["prettier --write", "eslint --fix", "git add"], - "+(__tests__|src)/*.+(ts|tsx)": ["prettier --write", "tslint -c tslint.json -p tsconfig.json -t verbose --fix", "git add"] -} diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 0000000..fd2ffe0 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "*.{ts,tsx,mts,cts,js,mjs,cjs}": ["oxfmt --write", "eslint --fix"], + "*.{json,md}": ["oxfmt --write"] +} diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..7433314 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +git-tag-version=false +prefer-dedupe=true +save-exact=true diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..78c8629 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "singleQuote": true, + "printWidth": 80, + "trailingComma": "all", + "embeddedLanguageFormatting": "auto" +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..8a686f0 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,591 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript", "unicorn", "import"], + "categories": { + "correctness": "off" + }, + "env": { + "builtin": true + }, + "ignorePatterns": [ + "coverage/**/*", + "dist/**/*", + "docs/**/*", + "node_modules/**/*", + "storybook-static/**/*" + ], + "rules": { + "typescript/no-implied-eval": "error", + "typescript/only-throw-error": "error", + "typescript/prefer-promise-reject-errors": "error", + "typescript/require-await": "error", + "typescript/await-thenable": "error", + "typescript/no-array-delete": "error", + "typescript/no-base-to-string": "error", + "typescript/no-deprecated": "error", + "typescript/no-duplicate-type-constituents": [ + "error", + { + "ignoreIntersections": true + } + ], + "typescript/no-floating-promises": "error", + "typescript/no-for-in-array": "error", + "typescript/no-meaningless-void-operator": [ + "error", + { + "checkNever": false + } + ], + "typescript/no-misused-promises": [ + "error", + { + "checksVoidReturn": true, + "checksConditionals": true + } + ], + "typescript/no-misused-spread": "error", + "typescript/no-mixed-enums": "error", + "typescript/no-redundant-type-constituents": "error", + "typescript/no-unnecessary-boolean-literal-compare": "error", + "typescript/no-unnecessary-condition": "error", + "typescript/no-unnecessary-template-expression": "error", + "typescript/no-unnecessary-type-arguments": "error", + "typescript/no-unnecessary-type-assertion": "error", + "typescript/no-unsafe-call": "error", + "typescript/no-unsafe-enum-comparison": "error", + "typescript/no-unsafe-member-access": "error", + "typescript/no-unsafe-type-assertion": "error", + "typescript/no-unsafe-unary-minus": "error", + "typescript/non-nullable-type-assertion-style": "error", + "typescript/prefer-includes": "error", + "typescript/prefer-nullish-coalescing": "error", + "typescript/prefer-optional-chain": "error", + "typescript/prefer-reduce-type-parameter": "error", + "typescript/prefer-return-this-type": "error", + "typescript/promise-function-async": "error", + "typescript/related-getter-setter-pairs": "error", + "typescript/require-array-sort-compare": "error", + "typescript/restrict-plus-operands": "error", + "typescript/restrict-template-expressions": "error", + "typescript/return-await": ["error", "always"], + "typescript/switch-exhaustiveness-check": "error", + "typescript/use-unknown-in-catch-callback-variable": "error" + }, + "overrides": [ + { + "files": ["**/*.{js,jsx,mjs,cjs,mjsx,cjsx}"], + "rules": { + "array-callback-return": "error", + "constructor-super": "error", + "for-direction": "error", + "getter-return": "error", + "no-async-promise-executor": "error", + "no-await-in-loop": "error", + "no-class-assign": "error", + "no-compare-neg-zero": "error", + "no-cond-assign": ["error", "except-parens"], + "no-const-assign": "error", + "no-constant-binary-expression": "error", + "no-constant-condition": "error", + "no-constructor-return": "error", + "no-control-regex": "error", + "no-debugger": "error", + "no-dupe-class-members": "error", + "no-dupe-else-if": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-duplicate-imports": "off", + "no-empty-character-class": "error", + "no-empty-pattern": "error", + "no-ex-assign": "error", + "no-fallthrough": [ + "error", + { + "commentPattern": "^no\\sbreak" + } + ], + "no-func-assign": "error", + "no-import-assign": "error", + "no-inner-declarations": "error", + "no-invalid-regexp": [ + "error", + { + "allowConstructorFlags": ["u", "y"] + } + ], + "no-irregular-whitespace": "error", + "no-loss-of-precision": "error", + "no-misleading-character-class": "error", + "no-new-native-nonconstructor": "error", + "no-obj-calls": "error", + "no-promise-executor-return": "error", + "no-prototype-builtins": "error", + "no-self-assign": [ + "error", + { + "props": true + } + ], + "no-self-compare": "error", + "no-setter-return": "error", + "no-sparse-arrays": "error", + "no-template-curly-in-string": "error", + "no-this-before-super": "error", + "no-unassigned-vars": "error", + "no-undef": "error", + "no-unmodified-loop-condition": "error", + "no-unreachable": "error", + "no-unsafe-finally": "error", + "no-unsafe-negation": "error", + "no-unsafe-optional-chaining": [ + "error", + { + "disallowArithmeticOperators": true + } + ], + "no-unused-private-class-members": "error", + "no-unused-vars": [ + "error", + { + "args": "all", + "argsIgnorePattern": "^_", + "caughtErrors": "all", + "caughtErrorsIgnorePattern": "^_", + "ignoreRestSiblings": true, + "vars": "all", + "varsIgnorePattern": "^_" + } + ], + "no-useless-backreference": "error", + "use-isnan": "error", + "valid-typeof": [ + "error", + { + "requireStringLiterals": false + } + ], + "accessor-pairs": "error", + "arrow-body-style": ["error", "as-needed"], + "block-scoped-var": "off", + "capitalized-comments": "off", + "class-methods-use-this": "error", + "complexity": "off", + "default-case": [ + "error", + { + "commentPattern": "^no\\sdefault" + } + ], + "default-case-last": "error", + "default-param-last": "error", + "eqeqeq": [ + "error", + "always", + { + "null": "ignore" + } + ], + "func-names": ["error", "always"], + "func-style": [ + "error", + "declaration", + { + "allowArrowFunctions": true + } + ], + "grouped-accessor-pairs": ["error", "getBeforeSet"], + "guard-for-in": "off", + "id-length": "off", + "init-declarations": "off", + "max-classes-per-file": ["error", 3], + "max-depth": ["error", 4], + "max-lines": "off", + "max-lines-per-function": "off", + "max-nested-callbacks": "off", + "max-params": [ + "error", + { + "max": 4 + } + ], + "max-statements": "off", + "new-cap": "off", + "no-alert": "error", + "no-array-constructor": "error", + "no-bitwise": "error", + "no-caller": "error", + "no-case-declarations": "error", + "no-console": [ + "error", + { + "allow": ["warn", "error"] + } + ], + "no-continue": "off", + "no-delete-var": "error", + "no-div-regex": "error", + "no-else-return": "error", + "no-empty": [ + "error", + { + "allowEmptyCatch": true + } + ], + "no-empty-function": "off", + "no-empty-static-block": "error", + "no-eq-null": "off", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-boolean-cast": "error", + "no-extra-label": "off", + "no-global-assign": "error", + "no-implicit-coercion": [ + "error", + { + "allow": ["!!"] + } + ], + "no-inline-comments": "off", + "no-iterator": "error", + "no-label-var": "off", + "no-labels": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-magic-numbers": "off", + "no-multi-assign": "error", + "no-multi-str": "error", + "no-negated-condition": "off", + "no-nested-ternary": "off", + "no-new": "error", + "no-new-func": "error", + "no-new-wrappers": "error", + "no-nonoctal-decimal-escape": "error", + "no-object-constructor": "error", + "no-param-reassign": [ + "error", + { + "props": false + } + ], + "no-plusplus": [ + "error", + { + "allowForLoopAfterthoughts": true + } + ], + "no-proto": "error", + "no-redeclare": "off", + "no-regex-spaces": "error", + "no-restricted-globals": "off", + "no-restricted-imports": "off", + "no-return-assign": ["error", "always"], + "no-script-url": "error", + "no-sequences": "error", + "no-shadow": "error", + "no-shadow-restricted-names": "error", + "no-ternary": "off", + "no-undefined": "off", + "no-unneeded-ternary": "error", + "no-unused-expressions": "error", + "no-unused-labels": "off", + "no-useless-call": "error", + "no-useless-catch": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "off", + "no-useless-constructor": "error", + "no-useless-escape": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "error", + "no-void": "error", + "no-warning-comments": "off", + "no-with": "error", + "operator-assignment": ["error", "always"], + "prefer-const": [ + "error", + { + "ignoreReadBeforeAssign": true + } + ], + "prefer-exponentiation-operator": "error", + "prefer-numeric-literals": "error", + "prefer-object-has-own": "error", + "prefer-object-spread": "error", + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "error", + "preserve-caught-error": [ + "error", + { + "requireCatchParameter": true + } + ], + "radix": "error", + "require-yield": "error", + "sort-imports": "off", + "sort-keys": "off", + "sort-vars": "off", + "symbol-description": "off", + "unicode-bom": ["error", "never"], + "vars-on-top": "off", + "yoda": ["error", "never"] + } + }, + { + "files": [ + "**/*.{js,jsx,mjs,cjs,mjsx,cjsx}", + "**/*.{ts,tsx,mts,cts,mtsx,ctsx}" + ], + "rules": { + "class-methods-use-this": [ + "error", + { + "ignoreOverrideMethods": true, + "ignoreClassesThatImplementAnInterface": true + } + ], + "default-param-last": "error", + "init-declarations": "off", + "max-params": [ + "error", + { + "max": 4 + } + ], + "no-dupe-class-members": "error", + "no-empty-function": "off", + "no-loop-func": "error", + "no-loss-of-precision": "off", + "no-magic-numbers": "off", + "no-redeclare": "off", + "no-restricted-imports": "off", + "no-shadow": "error", + "no-unused-expressions": "error", + "no-unused-private-class-members": "off", + "no-unused-vars": [ + "error", + { + "args": "all", + "argsIgnorePattern": "^_", + "caughtErrors": "all", + "caughtErrorsIgnorePattern": "^_", + "ignoreRestSiblings": true, + "vars": "all", + "varsIgnorePattern": "^_" + } + ], + "no-useless-constructor": "error", + "typescript/adjacent-overload-signatures": "error", + "typescript/array-type": [ + "error", + { + "default": "array-simple", + "readonly": "array-simple" + } + ], + "typescript/ban-ts-comment": [ + "error", + { + "ts-expect-error": "allow-with-description", + "ts-ignore": "allow-with-description", + "ts-nocheck": "allow-with-description", + "ts-check": "allow-with-description", + "minimumDescriptionLength": 3 + } + ], + "typescript/ban-tslint-comment": "error", + "typescript/class-literal-property-style": ["error", "fields"], + "typescript/consistent-generic-constructors": "error", + "typescript/consistent-indexed-object-style": ["error", "record"], + "typescript/consistent-type-assertions": [ + "error", + { + "assertionStyle": "as", + "objectLiteralTypeAssertions": "allow-as-parameter" + } + ], + "typescript/consistent-type-definitions": "off", + "typescript/consistent-type-imports": [ + "error", + { + "prefer": "type-imports", + "disallowTypeAnnotations": true, + "fixStyle": "inline-type-imports" + } + ], + "typescript/no-confusing-non-null-assertion": "error", + "typescript/no-duplicate-enum-values": "error", + "typescript/no-dynamic-delete": "error", + "typescript/no-empty-object-type": "error", + "typescript/no-explicit-any": "off", + "typescript/no-extra-non-null-assertion": "error", + "typescript/no-extraneous-class": "error", + "typescript/no-import-type-side-effects": "error", + "typescript/no-inferrable-types": [ + "error", + { + "ignoreParameters": true, + "ignoreProperties": true + } + ], + "typescript/no-invalid-void-type": "error", + "typescript/no-misused-new": "error", + "typescript/no-namespace": [ + "error", + { + "allowDefinitionFiles": true + } + ], + "typescript/no-non-null-asserted-nullish-coalescing": "error", + "typescript/no-non-null-asserted-optional-chain": "error", + "typescript/no-non-null-assertion": "off", + "typescript/no-require-imports": "error", + "typescript/no-restricted-types": "error", + "typescript/no-this-alias": [ + "error", + { + "allowDestructuring": true, + "allowedNames": ["self"] + } + ], + "typescript/no-unnecessary-type-constraint": "error", + "typescript/no-unsafe-declaration-merging": "off", + "typescript/no-unsafe-function-type": "error", + "typescript/no-useless-empty-export": "error", + "typescript/no-wrapper-object-types": "error", + "typescript/parameter-properties": "error", + "typescript/prefer-as-const": "error", + "typescript/prefer-enum-initializers": "error", + "typescript/prefer-for-of": "error", + "typescript/prefer-function-type": "error", + "typescript/prefer-literal-enum-member": [ + "error", + { + "allowBitwiseExpressions": true + } + ], + "typescript/prefer-namespace-keyword": "off", + "typescript/triple-slash-reference": [ + "error", + { + "path": "never", + "types": "never", + "lib": "never" + } + ], + "typescript/unified-signatures": "error", + "no-array-constructor": "error", + "no-use-before-define": [ + "error", + { + "classes": true, + "functions": true, + "variables": true, + "typedefs": true + } + ] + } + }, + { + "files": ["**/*.{ts,tsx,mts,cts,mtsx,ctsx}"], + "rules": { + "constructor-super": "off", + "getter-return": "off", + "no-const-assign": "off", + "no-dupe-keys": "off", + "no-func-assign": "off", + "no-import-assign": "off", + "no-obj-calls": "off", + "no-setter-return": "off", + "no-this-before-super": "off", + "no-undef": "off", + "no-unreachable": "off", + "no-unsafe-negation": "off", + "valid-typeof": "off", + "typescript/explicit-function-return-type": "off", + "typescript/explicit-module-boundary-types": [ + "error", + { + "allowArgumentsExplicitlyTypedAsAny": true, + "allowDirectConstAssertionInArrowFunctions": true, + "allowHigherOrderFunctions": true, + "allowTypedFunctionExpressions": true + } + ], + "typescript/no-unnecessary-parameter-property-assignment": "off" + } + }, + { + "files": ["**/*.{ts,tsx,mts,cts}"], + "rules": { + "import/consistent-type-specifier-style": "off", + "import/default": "error", + "import/export": "error", + "import/exports-last": "off", + "import/extensions": "off", + "import/first": "error", + "import/group-exports": "off", + "import/max-dependencies": "off", + "import/named": "error", + "import/namespace": "error", + "import/no-absolute-path": [ + "error", + { + "esmodule": false, + "commonjs": true + } + ], + "import/no-amd": "off", + "import/no-anonymous-default-export": "error", + "import/no-commonjs": "off", + "import/no-cycle": "error", + "import/no-default-export": "off", + "import/no-dynamic-require": "off", + "import/no-empty-named-blocks": "error", + "import/no-mutable-exports": "error", + "import/no-named-as-default": "off", + "import/no-named-as-default-member": "error", + "import/no-named-default": "error", + "import/no-named-export": "off", + "import/no-namespace": "off", + "import/no-nodejs-modules": "off", + "import/no-relative-parent-imports": "off", + "import/no-self-import": "error", + "import/no-webpack-loader-syntax": "error", + "import/unambiguous": "error" + } + }, + { + "files": [ + "**/*.spec.js", + "**/*.spec.ts", + "**/*.spec.jsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.test.ts", + "**/*.test.jsx", + "**/*.test.tsx" + ], + "rules": { + "max-classes-per-file": "off", + "no-unused-expressions": "off" + } + }, + { + "files": ["**/*.{ts,tsx,mts,cts}"], + "rules": { + "no-shadow": "allow", + // Broken. Cannot disable inline + "import/unambiguous": "allow", + // Broken. Many false positives + "typescript/no-unnecessary-condition": "allow", + // I use for loop a loot for perf reasons + "typescript/prefer-for-of": "off", + "typescript/no-empty-object-type": "off" + } + } + ] +} diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index e0afa49..0000000 --- a/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "bracketSpacing": false, - "printWidth": 80, - "trailingComma": "all", - "tabWidth": 2, - "singleQuote": true -} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..8756645 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "printWidth": 80, + "trailingComma": "all", + "embeddedLanguageFormatting": "auto" +} diff --git a/.storybook/addons.js b/.storybook/addons.js deleted file mode 100644 index a9c4098..0000000 --- a/.storybook/addons.js +++ /dev/null @@ -1,2 +0,0 @@ -require('@storybook/addon-knobs/register'); -require('@storybook/addon-options/register'); diff --git a/.storybook/config.js b/.storybook/config.js deleted file mode 100644 index 76bf651..0000000 --- a/.storybook/config.js +++ /dev/null @@ -1,11 +0,0 @@ -const {addParameters, configure} = require('@storybook/react'); - -addParameters({ - options: { - addonPanelInRight: true, - name: 'React Virtualized Tree', - selectedPanel: 'knobs', - }, -}); - -configure(() => require('../__stories__'), module); diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 0000000..f80da49 --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,9 @@ +import type { StorybookConfig } from '@storybook/react-vite'; + +const config: StorybookConfig = { + addons: [], + framework: '@storybook/react-vite', + stories: ['../__stories__/**/*.story.tsx'], +}; + +export default config; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 0000000..4dfb07d --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,24 @@ +import type { Preview } from '@storybook/react-vite'; + +const preview: Preview = { + decorators: [ + (Story) => ( +
+
+ +
+
+ ), + ], + parameters: { + layout: 'fullscreen', + }, +}; + +export default preview; diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js deleted file mode 100644 index 002f1ee..0000000 --- a/.storybook/webpack.config.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = ({config}) => { - config.devtool = 'eval'; - - config.module.rules.push({ - test: /\.(ts|tsx)$/, - loader: require.resolve('babel-loader'), - }); - - config.resolve.extensions.push('.ts', '.tsx'); - - return config; -}; diff --git a/__stories__/AsyncData.story.tsx b/__stories__/AsyncData.story.tsx index 51bdfb7..8b2e2f9 100644 --- a/__stories__/AsyncData.story.tsx +++ b/__stories__/AsyncData.story.tsx @@ -1,25 +1,16 @@ /* eslint-disable max-depth */ -import {boolean, number, withKnobs} from '@storybook/addon-knobs'; -import {storiesOf} from '@storybook/react'; -import React, {FC, useCallback, useMemo, useRef, useState} from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { type FC, useCallback, useMemo, useRef, useState } from 'react'; +import { AutoSizer } from 'react-virtualized-auto-sizer'; import { - FixedSizeNodeData, - FixedSizeNodePublicState, FixedSizeTree, - TreeWalker, - TreeWalkerValue, + type FixedSizeNodeData, + type FixedSizeNodePublicState, + type TreeWalker, + type TreeWalkerValue, } from '../src'; -import {NodeComponentProps} from '../src/Tree'; -import {AsyncTaskScheduler} from './utils'; - -document.body.style.margin = '0'; -document.body.style.display = 'flex'; -document.body.style.minHeight = '100vh'; - -const root = document.getElementById('root')!; -root.style.margin = '10px 0 0 10px'; -root.style.flex = '1'; +import type { NodeComponentProps } from '../src/Tree'; +import { AsyncTaskScheduler } from './utils'; type TreeNode = Readonly<{ children: TreeNode[]; @@ -64,8 +55,8 @@ const createNode = ( return node; }; -const defaultTextStyle = {marginLeft: 10}; -const defaultButtonStyle = {fontFamily: 'Courier New'}; +const defaultTextStyle = { marginLeft: 10 }; +const defaultButtonStyle = { fontFamily: 'Courier New' }; type NodeMeta = Readonly<{ nestingLevel: number; @@ -90,11 +81,10 @@ const getNodeData = ( node, }); -const Node: FC ->> = ({ - data: {download, downloaded, isLeaf, name, nestingLevel}, +const Node: FC< + NodeComponentProps> +> = ({ + data: { download, downloaded, isLeaf, name, nestingLevel }, isOpen, style, setOpen, @@ -140,7 +130,7 @@ type TreePresenterProps = Readonly<{ itemSize: number; }>; -const TreePresenter: FC = ({disableAsync, itemSize}) => { +const TreePresenter: FC = ({ disableAsync, itemSize }) => { const [downloadedIds, setDownloadedIds] = useState([]); const scheduler = useRef>( new AsyncTaskScheduler((ids) => { @@ -153,8 +143,8 @@ const TreePresenter: FC = ({disableAsync, itemSize}) => { return createNode(downloadedIds); }, [downloadedIds]); - const createDownloader = (node: TreeNode) => (): Promise => - new Promise((resolve) => { + const createDownloader = (node: TreeNode) => async (): Promise => + await new Promise((resolve) => { const timeoutId = setTimeout(() => { scheduler.current.finalize(); }, 2000); @@ -163,20 +153,19 @@ const TreePresenter: FC = ({disableAsync, itemSize}) => { }); const treeWalker = useCallback( - function* treeWalker(): ReturnType> { + function* asyncTreeWalker(): ReturnType> { yield getNodeData(rootNode, 0, createDownloader(rootNode)); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + // oxlint-disable-next-line typescript/no-unnecessary-condition while (true) { const parentMeta = yield; if (parentMeta.data.downloaded) { - // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < parentMeta.node.children.length; i++) { yield getNodeData( - parentMeta.node.children[i], + parentMeta.node.children[i]!, parentMeta.nestingLevel + 1, - createDownloader(parentMeta.node.children[i]), + createDownloader(parentMeta.node.children[i]!), ); } } @@ -186,27 +175,33 @@ const TreePresenter: FC = ({disableAsync, itemSize}) => { ); return ( - - {({height}) => ( + ( {Node} )} - + /> ); }; -storiesOf('Tree', module) - .addDecorator(withKnobs) - .add('Async data', () => ( - - )); +const meta: Meta = { + args: { + disableAsync: false, + itemSize: 30, + }, + component: TreePresenter, + title: 'Tree/AsyncData', +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/__stories__/AsyncDataIdle.story.tsx b/__stories__/AsyncDataIdle.story.tsx index 2a247bb..494eea1 100644 --- a/__stories__/AsyncDataIdle.story.tsx +++ b/__stories__/AsyncDataIdle.story.tsx @@ -1,33 +1,24 @@ -import {boolean, number, withKnobs} from '@storybook/addon-knobs'; -import {storiesOf} from '@storybook/react'; -import React, { - DependencyList, - FC, +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { + type DependencyList, + type FC, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; +import { AutoSizer } from 'react-virtualized-auto-sizer'; import { - FixedSizeNodeData, - FixedSizeNodePublicState, FixedSizeTree, - TreeWalker, - TreeWalkerValue, + type FixedSizeNodeData, + type FixedSizeNodePublicState, + type TreeWalker, + type TreeWalkerValue, } from '../src'; -import {NodeComponentProps} from '../src/Tree'; -import {noop} from '../src/utils'; -import {AsyncTaskScheduler} from './utils'; - -document.body.style.margin = '0'; -document.body.style.display = 'flex'; -document.body.style.minHeight = '100vh'; - -const root = document.getElementById('root')!; -root.style.margin = '10px 0 0 10px'; -root.style.flex = '1'; +import type { NodeComponentProps } from '../src/Tree'; +import { noop } from '../src/utils'; +import { AsyncTaskScheduler } from './utils'; type TreeNode = Readonly<{ children: TreeNode[]; @@ -72,8 +63,8 @@ const createNode = ( return node; }; -const defaultTextStyle = {marginLeft: 10}; -const defaultButtonStyle = {fontFamily: 'Courier New'}; +const defaultTextStyle = { marginLeft: 10 }; +const defaultButtonStyle = { fontFamily: 'Courier New' }; type NodeMeta = Readonly<{ nestingLevel: number; @@ -105,17 +96,16 @@ const useBuildingPromise = (deps: DependencyList) => { resolve.current(); }, deps); - return () => - new Promise((r) => { - resolve.current = r; + return async () => + await new Promise((nextResolve) => { + resolve.current = nextResolve; }); }; -const Node: FC ->> = ({ - data: {download, downloaded, isLeaf, name, nestingLevel}, +const Node: FC< + NodeComponentProps> +> = ({ + data: { download, downloaded, isLeaf, name, nestingLevel }, isOpen, style, setOpen, @@ -169,7 +159,7 @@ type TreePresenterProps = Readonly<{ itemSize: number; }>; -const TreePresenter: FC = ({disableAsync, itemSize}) => { +const TreePresenter: FC = ({ disableAsync, itemSize }) => { const [downloadedIds, setDownloadedIds] = useState([]); const scheduler = useRef>( new AsyncTaskScheduler(setDownloadedIds), @@ -180,8 +170,8 @@ const TreePresenter: FC = ({disableAsync, itemSize}) => { return createNode(downloadedIds); }, [downloadedIds]); - const createDownloader = (node: TreeNode) => (): Promise => - new Promise((resolve) => { + const createDownloader = (node: TreeNode) => async (): Promise => + await new Promise((resolve) => { const timeoutId = setTimeout(() => { scheduler.current.finalize(); }, 2000); @@ -190,20 +180,21 @@ const TreePresenter: FC = ({disableAsync, itemSize}) => { }); const treeWalker = useCallback( - function* treeWalker(): ReturnType> { + function* idleAsyncTreeWalker(): ReturnType< + TreeWalker + > { yield getNodeData(rootNode, 0, createDownloader(rootNode)); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + // oxlint-disable-next-line typescript/no-unnecessary-condition while (true) { const parentMeta = yield; if (parentMeta.data.downloaded) { - // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < parentMeta.node.children.length; i++) { yield getNodeData( - parentMeta.node.children[i], + parentMeta.node.children[i]!, parentMeta.nestingLevel + 1, - createDownloader(parentMeta.node.children[i]), + createDownloader(parentMeta.node.children[i]!), ); } } @@ -213,12 +204,12 @@ const TreePresenter: FC = ({disableAsync, itemSize}) => { ); return ( - - {({height}) => ( + ( = ({disableAsync, itemSize}) => { {Node} )} - + /> ); }; -storiesOf('Tree', module) - .addDecorator(withKnobs) - .add('Async data with placeholder', () => ( - - )); +const meta: Meta = { + args: { + disableAsync: false, + itemSize: 30, + }, + component: TreePresenter, + title: 'Tree/AsyncDataIdle', +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/__stories__/BigData.story.tsx b/__stories__/BigData.story.tsx index 89e34c9..f981101 100644 --- a/__stories__/BigData.story.tsx +++ b/__stories__/BigData.story.tsx @@ -1,24 +1,15 @@ /* eslint-disable max-depth */ -import {number, text, withKnobs} from '@storybook/addon-knobs'; -import {storiesOf} from '@storybook/react'; -import React, {FC, useCallback, useRef} from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { type FC, useCallback, useRef } from 'react'; +import { AutoSizer } from 'react-virtualized-auto-sizer'; import { - TreeWalker, - TreeWalkerValue, - VariableSizeNodeData, - VariableSizeNodePublicState, VariableSizeTree, + type TreeWalker, + type TreeWalkerValue, + type VariableSizeNodeData, + type VariableSizeNodePublicState, } from '../src'; -import {NodeComponentProps} from '../src/Tree'; - -document.body.style.margin = '0'; -document.body.style.display = 'flex'; -document.body.style.minHeight = '100vh'; - -const root = document.getElementById('root')!; -root.style.margin = '10px 0 0 10px'; -root.style.flex = '1'; +import type { NodeComponentProps } from '../src/Tree'; type TreeNode = Readonly<{ children: TreeNode[]; @@ -40,7 +31,7 @@ type ExtendedData = VariableSizeNodeData & let nodeId = 0; -const createNode = (depth: number = 0) => { +const createNode = (depth: number = 0): TreeNode => { const node: TreeNode = { children: [], id: nodeId, @@ -61,15 +52,14 @@ const createNode = (depth: number = 0) => { }; const rootNode = createNode(); -const defaultGapStyle = {marginLeft: 10}; -const defaultButtonStyle = {fontFamily: 'Courier New'}; +const defaultGapStyle = { marginLeft: 10 }; +const defaultButtonStyle = { fontFamily: 'Courier New' }; -const Node: FC ->> = ({ +const Node: FC< + NodeComponentProps> +> = ({ height, - data: {isLeaf, name, nestingLevel}, + data: { isLeaf, name, nestingLevel }, isOpen, resize, style, @@ -81,7 +71,7 @@ const Node: FC resize(canOpen ? height + halfSize : height - halfSize, true), - [height, resize], + [canOpen, halfSize, height, resize], ); return ( @@ -98,7 +88,7 @@ const Node: FC