From 6620dfa6c959661723f06ad8773bf74e0037e04e Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 14 Aug 2023 14:15:00 -0400 Subject: [PATCH 01/35] Installed Jest and deps --- package.json | 24 +- pnpm-lock.yaml | 3153 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 2604 insertions(+), 573 deletions(-) diff --git a/package.json b/package.json index 92893207a..a6e475c8f 100644 --- a/package.json +++ b/package.json @@ -21,19 +21,23 @@ "typescript:watch": "tsc --noEmit --watch" }, "devDependencies": { - "@babel/preset-typescript": "^7.21.5", - "@playwright/test": "^1.35.0", - "@types/node": "^18.15.11", - "@types/react": "^18.0.26", - "@types/react-dom": "^18.0.10", - "@typescript-eslint/eslint-plugin": "^5.57.0", - "@typescript-eslint/parser": "^5.57.0", - "@typescript-eslint/type-utils": "^5.57.0", - "eslint": "^8.37.0", + "@babel/preset-typescript": "^7.22.5", + "@playwright/test": "^1.37.0", + "@types/jest": "^29.5.3", + "@types/node": "^18.17.5", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "@typescript-eslint/type-utils": "^5.62.0", + "eslint": "^8.47.0", + "jest": "^29.6.2", + "jest-environment-jsdom": "^29.6.2", "parcel": "^2.9.3", "prettier": "latest", "process": "^0.11.10", - "typescript": ">=3.0.0" + "ts-jest": "^29.1.1", + "typescript": "^5.1.6" }, "dependencies": { "@parcel/config-default": "^2.9.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad6b77430..7083b1796 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + importers: .: @@ -21,50 +25,62 @@ importers: version: 2.9.3(@parcel/core@2.9.3) '@parcel/transformer-typescript-types': specifier: ^2.9.3 - version: 2.9.3(@parcel/core@2.9.3)(typescript@5.0.4) + version: 2.9.3(@parcel/core@2.9.3)(typescript@5.1.6) '@preconstruct/cli': specifier: ^2.8.1 version: 2.8.1 devDependencies: '@babel/preset-typescript': - specifier: ^7.21.5 - version: 7.21.5(@babel/core@7.21.8) + specifier: ^7.22.5 + version: 7.22.5(@babel/core@7.22.10) '@playwright/test': - specifier: ^1.35.0 - version: 1.35.0 + specifier: ^1.37.0 + version: 1.37.0 + '@types/jest': + specifier: ^29.5.3 + version: 29.5.3 '@types/node': - specifier: ^18.15.11 - version: 18.16.9 + specifier: ^18.17.5 + version: 18.17.5 '@types/react': - specifier: ^18.0.26 - version: 18.2.6 + specifier: ^18.2.20 + version: 18.2.20 '@types/react-dom': - specifier: ^18.0.10 - version: 18.2.4 + specifier: ^18.2.7 + version: 18.2.7 '@typescript-eslint/eslint-plugin': - specifier: ^5.57.0 - version: 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@5.0.4) + specifier: ^5.62.0 + version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.47.0)(typescript@5.1.6) '@typescript-eslint/parser': - specifier: ^5.57.0 - version: 5.59.5(eslint@8.40.0)(typescript@5.0.4) + specifier: ^5.62.0 + version: 5.62.0(eslint@8.47.0)(typescript@5.1.6) '@typescript-eslint/type-utils': - specifier: ^5.57.0 - version: 5.59.5(eslint@8.40.0)(typescript@5.0.4) + specifier: ^5.62.0 + version: 5.62.0(eslint@8.47.0)(typescript@5.1.6) eslint: - specifier: ^8.37.0 - version: 8.40.0 + specifier: ^8.47.0 + version: 8.47.0 + jest: + specifier: ^29.6.2 + version: 29.6.2(@types/node@18.17.5) + jest-environment-jsdom: + specifier: ^29.6.2 + version: 29.6.2 parcel: specifier: ^2.9.3 version: 2.9.3 prettier: specifier: latest - version: 3.0.0 + version: 3.0.1 process: specifier: ^0.11.10 version: 0.11.10 + ts-jest: + specifier: ^29.1.1 + version: 29.1.1(@babel/core@7.22.10)(jest@29.6.2)(typescript@5.1.6) typescript: - specifier: '>=3.0.0' - version: 5.0.4 + specifier: ^5.1.6 + version: 5.1.6 packages/react-resizable-panels: devDependencies: @@ -88,7 +104,7 @@ importers: dependencies: '@codemirror/lang-css': specifier: latest - version: 6.2.0(@codemirror/view@6.11.2) + version: 6.2.1(@codemirror/view@6.16.0) '@codemirror/lang-html': specifier: latest version: 6.4.5 @@ -140,326 +156,430 @@ importers: packages: + /@aashutoshrathi/word-wrap@1.2.6: + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + dev: true + /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} dependencies: '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.19 - /@babel/code-frame@7.21.4: - resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} + /@babel/code-frame@7.22.10: + resolution: {integrity: sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/highlight': 7.18.6 + '@babel/highlight': 7.22.10 + chalk: 2.4.2 - /@babel/compat-data@7.21.7: - resolution: {integrity: sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==} + /@babel/compat-data@7.22.9: + resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==} engines: {node: '>=6.9.0'} - /@babel/core@7.21.8: - resolution: {integrity: sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==} + /@babel/core@7.22.10: + resolution: {integrity: sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==} engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.21.4 - '@babel/generator': 7.21.5 - '@babel/helper-compilation-targets': 7.21.5(@babel/core@7.21.8) - '@babel/helper-module-transforms': 7.21.5 - '@babel/helpers': 7.21.5 - '@babel/parser': 7.21.8 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 + '@babel/code-frame': 7.22.10 + '@babel/generator': 7.22.10 + '@babel/helper-compilation-targets': 7.22.10 + '@babel/helper-module-transforms': 7.22.9(@babel/core@7.22.10) + '@babel/helpers': 7.22.10 + '@babel/parser': 7.22.10 + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.10 + '@babel/types': 7.22.10 convert-source-map: 1.9.0 debug: 4.3.4 gensync: 1.0.0-beta.2 json5: 2.2.3 - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color - /@babel/generator@7.21.5: - resolution: {integrity: sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==} + /@babel/generator@7.22.10: + resolution: {integrity: sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.10 '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.19 jsesc: 2.5.2 - /@babel/helper-annotate-as-pure@7.18.6: - resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} + /@babel/helper-annotate-as-pure@7.22.5: + resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.10 dev: true - /@babel/helper-compilation-targets@7.21.5(@babel/core@7.21.8): - resolution: {integrity: sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==} + /@babel/helper-compilation-targets@7.22.10: + resolution: {integrity: sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 dependencies: - '@babel/compat-data': 7.21.7 - '@babel/core': 7.21.8 - '@babel/helper-validator-option': 7.21.0 - browserslist: 4.21.5 + '@babel/compat-data': 7.22.9 + '@babel/helper-validator-option': 7.22.5 + browserslist: 4.21.10 lru-cache: 5.1.1 - semver: 6.3.0 + semver: 6.3.1 - /@babel/helper-create-class-features-plugin@7.21.8(@babel/core@7.21.8): - resolution: {integrity: sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw==} + /@babel/helper-create-class-features-plugin@7.22.10(@babel/core@7.22.10): + resolution: {integrity: sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.21.8 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-environment-visitor': 7.21.5 - '@babel/helper-function-name': 7.21.0 - '@babel/helper-member-expression-to-functions': 7.21.5 - '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/helper-replace-supers': 7.21.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 - '@babel/helper-split-export-declaration': 7.18.6 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.22.10 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-member-expression-to-functions': 7.22.5 + '@babel/helper-optimise-call-expression': 7.22.5 + '@babel/helper-replace-supers': 7.22.9(@babel/core@7.22.10) + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + semver: 6.3.1 dev: true - /@babel/helper-environment-visitor@7.21.5: - resolution: {integrity: sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==} + /@babel/helper-environment-visitor@7.22.5: + resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==} engines: {node: '>=6.9.0'} - /@babel/helper-function-name@7.21.0: - resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} + /@babel/helper-function-name@7.22.5: + resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.20.7 - '@babel/types': 7.21.5 + '@babel/template': 7.22.5 + '@babel/types': 7.22.10 - /@babel/helper-hoist-variables@7.18.6: - resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} + /@babel/helper-hoist-variables@7.22.5: + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.10 - /@babel/helper-member-expression-to-functions@7.21.5: - resolution: {integrity: sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg==} + /@babel/helper-member-expression-to-functions@7.22.5: + resolution: {integrity: sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.10 dev: true - /@babel/helper-module-imports@7.21.4: - resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==} + /@babel/helper-module-imports@7.22.5: + resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.10 - /@babel/helper-module-transforms@7.21.5: - resolution: {integrity: sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==} + /@babel/helper-module-transforms@7.22.9(@babel/core@7.22.10): + resolution: {integrity: sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: - '@babel/helper-environment-visitor': 7.21.5 - '@babel/helper-module-imports': 7.21.4 - '@babel/helper-simple-access': 7.21.5 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.22.10 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-module-imports': 7.22.5 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.5 - /@babel/helper-optimise-call-expression@7.18.6: - resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} + /@babel/helper-optimise-call-expression@7.22.5: + resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.10 dev: true - /@babel/helper-plugin-utils@7.21.5: - resolution: {integrity: sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==} + /@babel/helper-plugin-utils@7.22.5: + resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} engines: {node: '>=6.9.0'} - /@babel/helper-replace-supers@7.21.5: - resolution: {integrity: sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg==} + /@babel/helper-replace-supers@7.22.9(@babel/core@7.22.10): + resolution: {integrity: sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: - '@babel/helper-environment-visitor': 7.21.5 - '@babel/helper-member-expression-to-functions': 7.21.5 - '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.22.10 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-member-expression-to-functions': 7.22.5 + '@babel/helper-optimise-call-expression': 7.22.5 dev: true - /@babel/helper-simple-access@7.21.5: - resolution: {integrity: sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==} + /@babel/helper-simple-access@7.22.5: + resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.10 - /@babel/helper-skip-transparent-expression-wrappers@7.20.0: - resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} + /@babel/helper-skip-transparent-expression-wrappers@7.22.5: + resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.10 dev: true - /@babel/helper-split-export-declaration@7.18.6: - resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} + /@babel/helper-split-export-declaration@7.22.6: + resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.10 - /@babel/helper-string-parser@7.21.5: - resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==} + /@babel/helper-string-parser@7.22.5: + resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-identifier@7.19.1: - resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + /@babel/helper-validator-identifier@7.22.5: + resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-option@7.21.0: - resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} + /@babel/helper-validator-option@7.22.5: + resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==} engines: {node: '>=6.9.0'} - /@babel/helpers@7.21.5: - resolution: {integrity: sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==} + /@babel/helpers@7.22.10: + resolution: {integrity: sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.10 + '@babel/types': 7.22.10 transitivePeerDependencies: - supports-color - /@babel/highlight@7.18.6: - resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + /@babel/highlight@7.22.10: + resolution: {integrity: sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.19.1 + '@babel/helper-validator-identifier': 7.22.5 chalk: 2.4.2 js-tokens: 4.0.0 - /@babel/parser@7.21.8: - resolution: {integrity: sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==} + /@babel/parser@7.22.10: + resolution: {integrity: sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.10 + + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.22.10): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.22.10): + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.22.10): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.22.10): + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.22.10): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.22.10): + resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.22.10): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.22.10): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.22.10): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.22.10): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.22.10): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.22.10): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + dev: true - /@babel/plugin-syntax-jsx@7.21.4(@babel/core@7.21.8): - resolution: {integrity: sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==} + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.22.10): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-typescript@7.21.4(@babel/core@7.21.8): - resolution: {integrity: sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==} + /@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.22.10): + resolution: {integrity: sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-transform-modules-commonjs@7.21.5(@babel/core@7.21.8): - resolution: {integrity: sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ==} + /@babel/plugin-transform-modules-commonjs@7.22.5(@babel/core@7.22.10): + resolution: {integrity: sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.8 - '@babel/helper-module-transforms': 7.21.5 - '@babel/helper-plugin-utils': 7.21.5 - '@babel/helper-simple-access': 7.21.5 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.22.10 + '@babel/helper-module-transforms': 7.22.9(@babel/core@7.22.10) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-simple-access': 7.22.5 - /@babel/plugin-transform-typescript@7.21.3(@babel/core@7.21.8): - resolution: {integrity: sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==} + /@babel/plugin-transform-typescript@7.22.10(@babel/core@7.22.10): + resolution: {integrity: sha512-7++c8I/ymsDo4QQBAgbraXLzIM6jmfao11KgIBEYZRReWzNWH9NtNgJcyrZiXsOPh523FQm6LfpLyy/U5fn46A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.8 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-create-class-features-plugin': 7.21.8(@babel/core@7.21.8) - '@babel/helper-plugin-utils': 7.21.5 - '@babel/plugin-syntax-typescript': 7.21.4(@babel/core@7.21.8) - transitivePeerDependencies: - - supports-color + '@babel/core': 7.22.10 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-create-class-features-plugin': 7.22.10(@babel/core@7.22.10) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.22.10) dev: true - /@babel/preset-typescript@7.21.5(@babel/core@7.21.8): - resolution: {integrity: sha512-iqe3sETat5EOrORXiQ6rWfoOg2y68Cs75B9wNxdPW4kixJxh7aXQE1KPdWLDniC24T/6dSnguF33W9j/ZZQcmA==} + /@babel/preset-typescript@7.22.5(@babel/core@7.22.10): + resolution: {integrity: sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - '@babel/helper-validator-option': 7.21.0 - '@babel/plugin-syntax-jsx': 7.21.4(@babel/core@7.21.8) - '@babel/plugin-transform-modules-commonjs': 7.21.5(@babel/core@7.21.8) - '@babel/plugin-transform-typescript': 7.21.3(@babel/core@7.21.8) - transitivePeerDependencies: - - supports-color + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-validator-option': 7.22.5 + '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.10) + '@babel/plugin-transform-modules-commonjs': 7.22.5(@babel/core@7.22.10) + '@babel/plugin-transform-typescript': 7.22.10(@babel/core@7.22.10) dev: true - /@babel/runtime@7.21.5: - resolution: {integrity: sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==} + /@babel/runtime@7.22.10: + resolution: {integrity: sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==} engines: {node: '>=6.9.0'} dependencies: - regenerator-runtime: 0.13.11 + regenerator-runtime: 0.14.0 dev: false - /@babel/template@7.20.7: - resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} + /@babel/template@7.22.5: + resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.21.4 - '@babel/parser': 7.21.8 - '@babel/types': 7.21.5 + '@babel/code-frame': 7.22.10 + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 - /@babel/traverse@7.21.5: - resolution: {integrity: sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==} + /@babel/traverse@7.22.10: + resolution: {integrity: sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.21.4 - '@babel/generator': 7.21.5 - '@babel/helper-environment-visitor': 7.21.5 - '@babel/helper-function-name': 7.21.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.21.8 - '@babel/types': 7.21.5 + '@babel/code-frame': 7.22.10 + '@babel/generator': 7.22.10 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color - /@babel/types@7.21.5: - resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==} + /@babel/types@7.22.10: + resolution: {integrity: sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.21.5 - '@babel/helper-validator-identifier': 7.19.1 + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.5 to-fast-properties: 2.0.0 - /@codemirror/autocomplete@6.7.1(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.11.2)(@lezer/common@1.0.2): - resolution: {integrity: sha512-hSxf9S0uB+GV+gBsjY1FZNo53e1FFdzPceRfCfD1gWOnV6o21GfB5J5Wg9G/4h76XZMPrF0A6OCK/Rz5+V1egg==} + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true + + /@codemirror/autocomplete@6.9.0(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.3): + resolution: {integrity: sha512-Fbwm0V/Wn3BkEJZRhr0hi5BhCo5a7eBL6LYaliPjOSwCyfOpnjXY59HruSxOUNV+1OYer0Tgx1zRNQttjXyDog==} peerDependencies: '@codemirror/language': ^6.0.0 '@codemirror/state': ^6.0.0 @@ -468,18 +588,18 @@ packages: dependencies: '@codemirror/language': 6.8.0 '@codemirror/state': 6.2.1 - '@codemirror/view': 6.11.2 - '@lezer/common': 1.0.2 + '@codemirror/view': 6.16.0 + '@lezer/common': 1.0.3 dev: false - /@codemirror/lang-css@6.2.0(@codemirror/view@6.11.2): - resolution: {integrity: sha512-oyIdJM29AyRPM3+PPq1I2oIk8NpUfEN3kAM05XWDDs6o3gSneIKaVJifT2P+fqONLou2uIgXynFyMUDQvo/szA==} + /@codemirror/lang-css@6.2.1(@codemirror/view@6.16.0): + resolution: {integrity: sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==} dependencies: - '@codemirror/autocomplete': 6.7.1(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.11.2)(@lezer/common@1.0.2) + '@codemirror/autocomplete': 6.9.0(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.3) '@codemirror/language': 6.8.0 '@codemirror/state': 6.2.1 - '@lezer/common': 1.0.2 - '@lezer/css': 1.1.2 + '@lezer/common': 1.0.3 + '@lezer/css': 1.1.3 transitivePeerDependencies: - '@codemirror/view' dev: false @@ -487,70 +607,70 @@ packages: /@codemirror/lang-html@6.4.5: resolution: {integrity: sha512-dUCSxkIw2G+chaUfw3Gfu5kkN83vJQN8gfQDp9iEHsIZluMJA0YJveT12zg/28BJx+uPsbQ6VimKCgx3oJrZxA==} dependencies: - '@codemirror/autocomplete': 6.7.1(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.11.2)(@lezer/common@1.0.2) - '@codemirror/lang-css': 6.2.0(@codemirror/view@6.11.2) + '@codemirror/autocomplete': 6.9.0(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.3) + '@codemirror/lang-css': 6.2.1(@codemirror/view@6.16.0) '@codemirror/lang-javascript': 6.1.9 '@codemirror/language': 6.8.0 '@codemirror/state': 6.2.1 - '@codemirror/view': 6.11.2 - '@lezer/common': 1.0.2 - '@lezer/css': 1.1.2 - '@lezer/html': 1.3.4 + '@codemirror/view': 6.16.0 + '@lezer/common': 1.0.3 + '@lezer/css': 1.1.3 + '@lezer/html': 1.3.6 dev: false /@codemirror/lang-javascript@6.1.9: resolution: {integrity: sha512-z3jdkcqOEBT2txn2a87A0jSy6Te3679wg/U8QzMeftFt+4KA6QooMwfdFzJiuC3L6fXKfTXZcDocoaxMYfGz0w==} dependencies: - '@codemirror/autocomplete': 6.7.1(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.11.2)(@lezer/common@1.0.2) + '@codemirror/autocomplete': 6.9.0(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.3) '@codemirror/language': 6.8.0 - '@codemirror/lint': 6.2.1 + '@codemirror/lint': 6.4.0 '@codemirror/state': 6.2.1 - '@codemirror/view': 6.11.2 - '@lezer/common': 1.0.2 - '@lezer/javascript': 1.4.3 + '@codemirror/view': 6.16.0 + '@lezer/common': 1.0.3 + '@lezer/javascript': 1.4.5 dev: false /@codemirror/lang-markdown@6.2.0: resolution: {integrity: sha512-deKegEQVzfBAcLPqsJEa+IxotqPVwWZi90UOEvQbfa01NTAw8jNinrykuYPTULGUj+gha0ZG2HBsn4s5d64Qrg==} dependencies: - '@codemirror/autocomplete': 6.7.1(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.11.2)(@lezer/common@1.0.2) + '@codemirror/autocomplete': 6.9.0(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.16.0)(@lezer/common@1.0.3) '@codemirror/lang-html': 6.4.5 '@codemirror/language': 6.8.0 '@codemirror/state': 6.2.1 - '@codemirror/view': 6.11.2 - '@lezer/common': 1.0.2 - '@lezer/markdown': 1.0.2 + '@codemirror/view': 6.16.0 + '@lezer/common': 1.0.3 + '@lezer/markdown': 1.1.0 dev: false /@codemirror/language@6.8.0: resolution: {integrity: sha512-r1paAyWOZkfY0RaYEZj3Kul+MiQTEbDvYqf8gPGaRvNneHXCmfSaAVFjwRUPlgxS8yflMxw2CTu6uCMp8R8A2g==} dependencies: '@codemirror/state': 6.2.1 - '@codemirror/view': 6.11.2 - '@lezer/common': 1.0.2 + '@codemirror/view': 6.16.0 + '@lezer/common': 1.0.3 '@lezer/highlight': 1.1.6 - '@lezer/lr': 1.3.4 + '@lezer/lr': 1.3.9 style-mod: 4.0.3 dev: false - /@codemirror/lint@6.2.1: - resolution: {integrity: sha512-y1muai5U/uUPAGRyHMx9mHuHLypPcHWxzlZGknp/U5Mdb5Ol8Q5ZLp67UqyTbNFJJ3unVxZ8iX3g1fMN79S1JQ==} + /@codemirror/lint@6.4.0: + resolution: {integrity: sha512-6VZ44Ysh/Zn07xrGkdtNfmHCbGSHZzFBdzWi0pbd7chAQ/iUcpLGX99NYRZTa7Ugqg4kEHCqiHhcZnH0gLIgSg==} dependencies: '@codemirror/state': 6.2.1 - '@codemirror/view': 6.11.2 - crelt: 1.0.5 + '@codemirror/view': 6.16.0 + crelt: 1.0.6 dev: false /@codemirror/state@6.2.1: resolution: {integrity: sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==} dev: false - /@codemirror/view@6.11.2: - resolution: {integrity: sha512-AzxJ9Aub6ubBvoPBGvjcd4zITqcBBiLpJ89z0ZjnphOHncbvUvQcb9/WMVGpuwTT95+jW4knkH6gFIy0oLdaUQ==} + /@codemirror/view@6.16.0: + resolution: {integrity: sha512-1Z2HkvkC3KR/oEZVuW9Ivmp8TWLzGEd8T8TA04TTwPvqogfkHBdYSlflytDOqmkUxM2d1ywTg7X2dU5mC+SXvg==} dependencies: '@codemirror/state': 6.2.1 style-mod: 4.0.3 - w3c-keyname: 2.2.6 + w3c-keyname: 2.2.8 dev: false /@eslint-community/eslint-utils@4.4.0(eslint@8.40.0): @@ -563,11 +683,26 @@ packages: eslint-visitor-keys: 3.4.1 dev: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.47.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.47.0 + eslint-visitor-keys: 3.4.1 + dev: true + /@eslint-community/regexpp@4.5.1: resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true + /@eslint-community/regexpp@4.6.2: + resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + /@eslint/eslintrc@2.0.3: resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -585,11 +720,44 @@ packages: - supports-color dev: true + /@eslint/eslintrc@2.1.2: + resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.21.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + /@eslint/js@8.40.0: resolution: {integrity: sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@eslint/js@8.47.0: + resolution: {integrity: sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@humanwhocodes/config-array@0.11.10: + resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + /@humanwhocodes/config-array@0.11.8: resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} @@ -610,74 +778,301 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@istanbuljs/load-nyc-config@1.1.0: + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + dev: true + + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true + + /@jest/console@29.6.2: + resolution: {integrity: sha512-0N0yZof5hi44HAR2pPS+ikJ3nzKNoZdVu8FffRf3wy47I7Dm7etk/3KetMdRUqzVd16V4O2m2ISpNTbnIuqy1w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.1 + '@types/node': 18.17.5 + chalk: 4.1.2 + jest-message-util: 29.6.2 + jest-util: 29.6.2 + slash: 3.0.0 + dev: true + + /@jest/core@29.6.2: + resolution: {integrity: sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/console': 29.6.2 + '@jest/reporters': 29.6.2 + '@jest/test-result': 29.6.2 + '@jest/transform': 29.6.2 + '@jest/types': 29.6.1 + '@types/node': 18.17.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.8.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.5.0 + jest-config: 29.6.2(@types/node@18.17.5) + jest-haste-map: 29.6.2 + jest-message-util: 29.6.2 + jest-regex-util: 29.4.3 + jest-resolve: 29.6.2 + jest-resolve-dependencies: 29.6.2 + jest-runner: 29.6.2 + jest-runtime: 29.6.2 + jest-snapshot: 29.6.2 + jest-util: 29.6.2 + jest-validate: 29.6.2 + jest-watcher: 29.6.2 + micromatch: 4.0.5 + pretty-format: 29.6.2 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + dev: true + + /@jest/environment@29.6.2: + resolution: {integrity: sha512-AEcW43C7huGd/vogTddNNTDRpO6vQ2zaQNrttvWV18ArBx9Z56h7BIsXkNFJVOO4/kblWEQz30ckw0+L3izc+Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/fake-timers': 29.6.2 + '@jest/types': 29.6.1 + '@types/node': 18.17.5 + jest-mock: 29.6.2 + dev: true + + /@jest/expect-utils@29.6.2: + resolution: {integrity: sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.4.3 + dev: true + + /@jest/expect@29.6.2: + resolution: {integrity: sha512-m6DrEJxVKjkELTVAztTLyS/7C92Y2b0VYqmDROYKLLALHn8T/04yPs70NADUYPrV3ruI+H3J0iUIuhkjp7vkfg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + expect: 29.6.2 + jest-snapshot: 29.6.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/fake-timers@29.6.2: + resolution: {integrity: sha512-euZDmIlWjm1Z0lJ1D0f7a0/y5Kh/koLFMUBE5SUYWrmy8oNhJpbTBDAP6CxKnadcMLDoDf4waRYCe35cH6G6PA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.1 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 18.17.5 + jest-message-util: 29.6.2 + jest-mock: 29.6.2 + jest-util: 29.6.2 + dev: true + + /@jest/globals@29.6.2: + resolution: {integrity: sha512-cjuJmNDjs6aMijCmSa1g2TNG4Lby/AeU7/02VtpW+SLcZXzOLK2GpN2nLqcFjmhy3B3AoPeQVx7BnyOf681bAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.6.2 + '@jest/expect': 29.6.2 + '@jest/types': 29.6.1 + jest-mock: 29.6.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/reporters@29.6.2: + resolution: {integrity: sha512-sWtijrvIav8LgfJZlrGCdN0nP2EWbakglJY49J1Y5QihcQLfy7ovyxxjJBRXMNltgt4uPtEcFmIMbVshEDfFWw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.6.2 + '@jest/test-result': 29.6.2 + '@jest/transform': 29.6.2 + '@jest/types': 29.6.1 + '@jridgewell/trace-mapping': 0.3.19 + '@types/node': 18.17.5 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-instrument: 5.2.1 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.6 + jest-message-util: 29.6.2 + jest-util: 29.6.2 + jest-worker: 29.6.2 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.1.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/schemas@29.6.0: + resolution: {integrity: sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + + /@jest/source-map@29.6.0: + resolution: {integrity: sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jridgewell/trace-mapping': 0.3.19 + callsites: 3.1.0 + graceful-fs: 4.2.11 + dev: true + + /@jest/test-result@29.6.2: + resolution: {integrity: sha512-3VKFXzcV42EYhMCsJQURptSqnyjqCGbtLuX5Xxb6Pm6gUf1wIRIl+mandIRGJyWKgNKYF9cnstti6Ls5ekduqw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.6.2 + '@jest/types': 29.6.1 + '@types/istanbul-lib-coverage': 2.0.4 + collect-v8-coverage: 1.0.2 + dev: true + + /@jest/test-sequencer@29.6.2: + resolution: {integrity: sha512-GVYi6PfPwVejO7slw6IDO0qKVum5jtrJ3KoLGbgBWyr2qr4GaxFV6su+ZAjdTX75Sr1DkMFRk09r2ZVa+wtCGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.6.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.6.2 + slash: 3.0.0 + dev: true + + /@jest/transform@29.6.2: + resolution: {integrity: sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/core': 7.22.10 + '@jest/types': 29.6.1 + '@jridgewell/trace-mapping': 0.3.19 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.6.2 + jest-regex-util: 29.4.3 + jest-util: 29.6.2 + micromatch: 4.0.5 + pirates: 4.0.6 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/types@29.6.1: + resolution: {integrity: sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.0 + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 18.17.5 + '@types/yargs': 17.0.24 + chalk: 4.1.2 + dev: true + /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.19 - /@jridgewell/resolve-uri@3.1.0: - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} /@jridgewell/set-array@1.1.2: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} engines: {node: '>=6.0.0'} - /@jridgewell/source-map@0.3.3: - resolution: {integrity: sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==} + /@jridgewell/source-map@0.3.5: + resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} dependencies: '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.19 dev: false - /@jridgewell/sourcemap-codec@1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - /@jridgewell/trace-mapping@0.3.18: - resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} + /@jridgewell/trace-mapping@0.3.19: + resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 /@lezer/common@0.15.12: resolution: {integrity: sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig==} - /@lezer/common@1.0.2: - resolution: {integrity: sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==} + /@lezer/common@1.0.3: + resolution: {integrity: sha512-JH4wAXCgUOcCGNekQPLhVeUtIqjH0yPBs7vvUdSjyQama9618IOKFJwkv2kcqdhF0my8hQEgCTEJU0GIgnahvA==} dev: false - /@lezer/css@1.1.2: - resolution: {integrity: sha512-5TKMAReXukfEmIiZprDlGfZVfOOCyEStFi1YLzxclm9H3G/HHI49/2wzlRT6bQw5r7PoZVEtjTItEkb/UuZQyg==} + /@lezer/css@1.1.3: + resolution: {integrity: sha512-SjSM4pkQnQdJDVc80LYzEaMiNy9txsFbI7HsMgeVF28NdLaAdHNtQ+kB/QqDUzRBV/75NTXjJ/R5IdC8QQGxMg==} dependencies: '@lezer/highlight': 1.1.6 - '@lezer/lr': 1.3.4 + '@lezer/lr': 1.3.9 dev: false /@lezer/highlight@1.1.6: resolution: {integrity: sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==} dependencies: - '@lezer/common': 1.0.2 + '@lezer/common': 1.0.3 dev: false - /@lezer/html@1.3.4: - resolution: {integrity: sha512-HdJYMVZcT4YsMo7lW3ipL4NoyS2T67kMPuSVS5TgLGqmaCjEU/D6xv7zsa1ktvTK5lwk7zzF1e3eU6gBZIPm5g==} + /@lezer/html@1.3.6: + resolution: {integrity: sha512-Kk9HJARZTc0bAnMQUqbtuhFVsB4AnteR2BFUWfZV7L/x1H0aAKz6YabrfJ2gk/BEgjh9L3hg5O4y2IDZRBdzuQ==} dependencies: - '@lezer/common': 1.0.2 + '@lezer/common': 1.0.3 '@lezer/highlight': 1.1.6 - '@lezer/lr': 1.3.4 + '@lezer/lr': 1.3.9 dev: false - /@lezer/javascript@1.4.3: - resolution: {integrity: sha512-k7Eo9z9B1supZ5cCD4ilQv/RZVN30eUQL+gGbr6ybrEY3avBAL5MDiYi2aa23Aj0A79ry4rJRvPAwE2TM8bd+A==} + /@lezer/javascript@1.4.5: + resolution: {integrity: sha512-FmBUHz8K1V22DgjTd6SrIG9owbzOYZ1t3rY6vGEmw+e2RVBd7sqjM8uXEVRFmfxKFn1Mx2ABJehHjrN3G2ZpmA==} dependencies: '@lezer/highlight': 1.1.6 - '@lezer/lr': 1.3.4 + '@lezer/lr': 1.3.9 dev: false /@lezer/lr@0.15.8: @@ -685,16 +1080,16 @@ packages: dependencies: '@lezer/common': 0.15.12 - /@lezer/lr@1.3.4: - resolution: {integrity: sha512-7o+e4og/QoC/6btozDPJqnzBhUaD1fMfmvnEKQO1wRRiTse1WxaJ3OMEXZJnkgT6HCcTVOctSoXK9jGJw2oe9g==} + /@lezer/lr@1.3.9: + resolution: {integrity: sha512-XPz6dzuTHlnsbA5M2DZgjflNQ+9Hi5Swhic0RULdp3oOs3rh6bqGZolosVqN/fQIT8uNiepzINJDnS39oweTHQ==} dependencies: - '@lezer/common': 1.0.2 + '@lezer/common': 1.0.3 dev: false - /@lezer/markdown@1.0.2: - resolution: {integrity: sha512-8CY0OoZ6V5EzPjSPeJ4KLVbtXdLBd8V6sRCooN5kHnO28ytreEGTyrtU/zUwo/XLRzGr/e1g44KlzKi3yWGB5A==} + /@lezer/markdown@1.1.0: + resolution: {integrity: sha512-JYOI6Lkqbl83semCANkO3CKbKc0pONwinyagBufWBm+k4yhIcqfCF8B8fpEpvJLmIy7CAfwiq7dQ/PzUZA340g==} dependencies: - '@lezer/common': 1.0.2 + '@lezer/common': 1.0.3 '@lezer/highlight': 1.1.6 dev: false @@ -914,14 +1309,14 @@ packages: '@parcel/workers': 2.9.3(@parcel/core@2.9.3) abortcontroller-polyfill: 1.7.5 base-x: 3.0.9 - browserslist: 4.21.5 + browserslist: 4.21.10 clone: 2.1.2 dotenv: 7.0.0 dotenv-expand: 5.1.0 json5: 2.2.3 - msgpackr: 1.9.1 + msgpackr: 1.9.7 nullthrows: 1.1.1 - semver: 7.5.3 + semver: 7.5.4 /@parcel/diagnostic@2.9.3: resolution: {integrity: sha512-6jxBdyB3D7gP4iE66ghUGntWt2v64E6EbD4AetZk+hNJpgudOOPsKTovcMi/i7I4V0qD7WXSF4tvkZUoac0jwA==} @@ -948,7 +1343,7 @@ packages: '@parcel/fs-search': 2.9.3 '@parcel/types': 2.9.3(@parcel/core@2.9.3) '@parcel/utils': 2.9.3 - '@parcel/watcher': 2.1.0 + '@parcel/watcher': 2.2.0 '@parcel/workers': 2.9.3(@parcel/core@2.9.3) /@parcel/graph@2.9.3: @@ -995,7 +1390,7 @@ packages: '@parcel/fs': 2.9.3(@parcel/core@2.9.3) '@parcel/utils': 2.9.3 nullthrows: 1.1.1 - semver: 7.5.3 + semver: 7.5.4 transitivePeerDependencies: - '@parcel/core' @@ -1007,8 +1402,8 @@ packages: '@parcel/plugin': 2.9.3(@parcel/core@2.9.3) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.9.3 - browserslist: 4.21.5 - lightningcss: 1.20.0 + browserslist: 4.21.10 + lightningcss: 1.21.5 nullthrows: 1.1.1 transitivePeerDependencies: - '@parcel/core' @@ -1063,7 +1458,7 @@ packages: '@parcel/plugin': 2.9.3(@parcel/core@2.9.3) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.9.3 - '@swc/core': 1.3.60 + '@swc/core': 1.3.76 nullthrows: 1.1.1 transitivePeerDependencies: - '@parcel/core' @@ -1083,7 +1478,7 @@ packages: '@parcel/types': 2.9.3(@parcel/core@2.9.3) '@parcel/utils': 2.9.3 '@parcel/workers': 2.9.3(@parcel/core@2.9.3) - semver: 7.5.3 + semver: 7.5.4 /@parcel/packager-css@2.9.3(@parcel/core@2.9.3): resolution: {integrity: sha512-mePiWiYZOULY6e1RdAIJyRoYqXqGci0srOaVZYaP7mnrzvJgA63kaZFFsDiEWghunQpMUuUjM2x/vQVHzxmhKQ==} @@ -1118,7 +1513,7 @@ packages: '@parcel/plugin': 2.9.3(@parcel/core@2.9.3) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.9.3 - globals: 13.20.0 + globals: 13.21.0 nullthrows: 1.1.1 transitivePeerDependencies: - '@parcel/core' @@ -1265,10 +1660,10 @@ packages: '@parcel/plugin': 2.9.3(@parcel/core@2.9.3) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.9.3 - browserslist: 4.21.5 + browserslist: 4.21.10 json5: 2.2.3 nullthrows: 1.1.1 - semver: 7.5.3 + semver: 7.5.4 transitivePeerDependencies: - '@parcel/core' @@ -1280,8 +1675,8 @@ packages: '@parcel/plugin': 2.9.3(@parcel/core@2.9.3) '@parcel/source-map': 2.1.1 '@parcel/utils': 2.9.3 - browserslist: 4.21.5 - lightningcss: 1.20.0 + browserslist: 4.21.10 + lightningcss: 1.21.5 nullthrows: 1.1.1 transitivePeerDependencies: - '@parcel/core' @@ -1297,7 +1692,7 @@ packages: posthtml: 0.16.6 posthtml-parser: 0.10.2 posthtml-render: 3.0.0 - semver: 7.5.3 + semver: 7.5.4 srcset: 4.0.0 transitivePeerDependencies: - '@parcel/core' @@ -1327,10 +1722,10 @@ packages: '@parcel/utils': 2.9.3 '@parcel/workers': 2.9.3(@parcel/core@2.9.3) '@swc/helpers': 0.5.1 - browserslist: 4.21.5 + browserslist: 4.21.10 nullthrows: 1.1.1 regenerator-runtime: 0.13.11 - semver: 7.5.3 + semver: 7.5.4 /@parcel/transformer-json@2.9.3(@parcel/core@2.9.3): resolution: {integrity: sha512-yNL27dbOLhkkrjaQjiQ7Im9VOxmkfuuSNSmS0rA3gEjVcm07SLKRzWkAaPnyx44Lb6bzyOTWwVrb9aMmxgADpA==} @@ -1352,7 +1747,7 @@ packages: clone: 2.1.2 nullthrows: 1.1.1 postcss-value-parser: 4.2.0 - semver: 7.5.3 + semver: 7.5.4 transitivePeerDependencies: - '@parcel/core' @@ -1366,7 +1761,7 @@ packages: posthtml: 0.16.6 posthtml-parser: 0.10.2 posthtml-render: 3.0.0 - semver: 7.5.3 + semver: 7.5.4 transitivePeerDependencies: - '@parcel/core' @@ -1399,11 +1794,11 @@ packages: posthtml: 0.16.6 posthtml-parser: 0.10.2 posthtml-render: 3.0.0 - semver: 7.5.3 + semver: 7.5.4 transitivePeerDependencies: - '@parcel/core' - /@parcel/transformer-typescript-types@2.9.3(@parcel/core@2.9.3)(typescript@5.0.4): + /@parcel/transformer-typescript-types@2.9.3(@parcel/core@2.9.3)(typescript@5.1.6): resolution: {integrity: sha512-W+Ze3aUTdZuBQokXlkEQ/1hUApUm6VRyYzPqEs9jcqCqU8mv18i5ZGAz4bMuIJOBprp7M2wt10SJJx/SC1pl1A==} engines: {node: '>= 12.0.0', parcel: ^2.9.3} peerDependencies: @@ -1412,22 +1807,22 @@ packages: '@parcel/diagnostic': 2.9.3 '@parcel/plugin': 2.9.3(@parcel/core@2.9.3) '@parcel/source-map': 2.1.1 - '@parcel/ts-utils': 2.9.3(typescript@5.0.4) + '@parcel/ts-utils': 2.9.3(typescript@5.1.6) '@parcel/utils': 2.9.3 nullthrows: 1.1.1 - typescript: 5.0.4 + typescript: 5.1.6 transitivePeerDependencies: - '@parcel/core' dev: false - /@parcel/ts-utils@2.9.3(typescript@5.0.4): + /@parcel/ts-utils@2.9.3(typescript@5.1.6): resolution: {integrity: sha512-MiQoXFV8I4IWZT/q5yolKN/gnEY5gZfGB2X7W9WHJbRgyjlT/A5cPERXzVBj6mc3/VM1GdZJz76w637GUcQhow==} engines: {node: '>= 12.0.0'} peerDependencies: typescript: '>=3.0.0' dependencies: nullthrows: 1.1.1 - typescript: 5.0.4 + typescript: 5.1.6 dev: false /@parcel/types@2.9.3(@parcel/core@2.9.3): @@ -1456,49 +1851,140 @@ packages: chalk: 4.1.2 nullthrows: 1.1.1 - /@parcel/watcher@2.1.0: - resolution: {integrity: sha512-8s8yYjd19pDSsBpbkOHnT6Z2+UJSuLQx61pCFM0s5wSRvKCEMDjd/cHY3/GI1szHIWbpXpsJdg3V6ISGGx9xDw==} + /@parcel/watcher-android-arm64@2.2.0: + resolution: {integrity: sha512-nU2wh00CTQT9rr1TIKTjdQ9lAGYpmz6XuKw0nAwAN+S2A5YiD55BK1u+E5WMCT8YOIDe/n6gaj4o/Bi9294SSQ==} engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] requiresBuild: true - dependencies: - is-glob: 4.0.3 - micromatch: 4.0.5 - node-addon-api: 3.2.1 - node-gyp-build: 4.6.0 + optional: true - /@parcel/workers@2.9.3(@parcel/core@2.9.3): - resolution: {integrity: sha512-zRrDuZJzTevrrwElYosFztgldhqW6G9q5zOeQXfVQFkkEJCNfg36ixeiofKRU8uu2x+j+T6216mhMNB6HiuY+w==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@parcel/core': ^2.9.3 - dependencies: - '@parcel/core': 2.9.3 - '@parcel/diagnostic': 2.9.3 - '@parcel/logger': 2.9.3 - '@parcel/profiler': 2.9.3 - '@parcel/types': 2.9.3(@parcel/core@2.9.3) - '@parcel/utils': 2.9.3 - nullthrows: 1.1.1 + /@parcel/watcher-darwin-arm64@2.2.0: + resolution: {integrity: sha512-cJl0UZDcodciy3TDMomoK/Huxpjlkkim3SyMgWzjovHGOZKNce9guLz2dzuFwfObBFCjfznbFMIvAZ5syXotYw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true - /@playwright/test@1.35.0: - resolution: {integrity: sha512-6qXdd5edCBynOwsz1YcNfgX8tNWeuS9fxy5o59D0rvHXxRtjXRebB4gE4vFVfEMXl/z8zTnAzfOs7aQDEs8G4Q==} - engines: {node: '>=16'} - hasBin: true - dependencies: - '@types/node': 18.16.9 - playwright-core: 1.35.0 - optionalDependencies: - fsevents: 2.3.2 - dev: true + /@parcel/watcher-darwin-x64@2.2.0: + resolution: {integrity: sha512-QI77zxaGrCV1StKcoRYfsUfmUmvPMPfQrubkBBy5XujV2fwaLgZivQOTQMBgp5K2+E19u1ufpspKXAPqSzpbyg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true - /@preconstruct/cli@2.8.1: - resolution: {integrity: sha512-PX5w+au06iY/QaT+9RLmRlIfavRCRoMTC/krwtNrgPEnubR9e6P+QlywrKmwiEvkzbR9AEzGnRZL8uNRDDMzrQ==} - hasBin: true - dependencies: - '@babel/code-frame': 7.21.4 - '@babel/core': 7.21.8 - '@babel/helper-module-imports': 7.21.4 - '@babel/runtime': 7.21.5 + /@parcel/watcher-linux-arm-glibc@2.2.0: + resolution: {integrity: sha512-I2GPBcAXazPzabCmfsa3HRRW+MGlqxYd8g8RIueJU+a4o5nyNZDz0CR1cu0INT0QSQXEZV7w6UE8Hz9CF8u3Pg==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@parcel/watcher-linux-arm64-glibc@2.2.0: + resolution: {integrity: sha512-St5mlfp+2lS9AmgixUqfwJa/DwVmTCJxC1HcOubUTz6YFOKIlkHCeUa1Bxi4E/tR/HSez8+heXHL8HQkJ4Bd8g==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@parcel/watcher-linux-arm64-musl@2.2.0: + resolution: {integrity: sha512-jS+qfhhoOBVWwMLP65MaG8xdInMK30pPW8wqTCg2AAuVJh5xepMbzkhHJ4zURqHiyY3EiIRuYu4ONJKCxt8iqA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@parcel/watcher-linux-x64-glibc@2.2.0: + resolution: {integrity: sha512-xJvJ7R2wJdi47WZBFS691RDOWvP1j/IAs3EXaWVhDI8FFITbWrWaln7KoNcR0Y3T+ZwimFY/cfb0PNht1q895g==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@parcel/watcher-linux-x64-musl@2.2.0: + resolution: {integrity: sha512-D+NMpgr23a+RI5mu8ZPKWy7AqjBOkURFDgP5iIXXEf/K3hm0jJ3ogzi0Ed2237B/CdYREimCgXyeiAlE/FtwyA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@parcel/watcher-win32-arm64@2.2.0: + resolution: {integrity: sha512-z225cPn3aygJsyVUOWwfyW+fY0Tvk7N3XCOl66qUPFxpbuXeZuiuuJemmtm8vxyqa3Ur7peU/qJxrpC64aeI7Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /@parcel/watcher-win32-x64@2.2.0: + resolution: {integrity: sha512-JqGW0RJ61BkKx+yYzIURt9s53P7xMVbv0uxYPzAXLBINGaFmkIKSuUPyBVfy8TMbvp93lvF4SPBNDzVRJfvgOw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + + /@parcel/watcher@2.2.0: + resolution: {integrity: sha512-71S4TF+IMyAn24PK4KSkdKtqJDR3zRzb0HE3yXpacItqTM7XfF2f5q9NEGLEVl0dAaBAGfNwDCjH120y25F6Tg==} + engines: {node: '>= 10.0.0'} + requiresBuild: true + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.5 + node-addon-api: 7.0.0 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.2.0 + '@parcel/watcher-darwin-arm64': 2.2.0 + '@parcel/watcher-darwin-x64': 2.2.0 + '@parcel/watcher-linux-arm-glibc': 2.2.0 + '@parcel/watcher-linux-arm64-glibc': 2.2.0 + '@parcel/watcher-linux-arm64-musl': 2.2.0 + '@parcel/watcher-linux-x64-glibc': 2.2.0 + '@parcel/watcher-linux-x64-musl': 2.2.0 + '@parcel/watcher-win32-arm64': 2.2.0 + '@parcel/watcher-win32-x64': 2.2.0 + + /@parcel/workers@2.9.3(@parcel/core@2.9.3): + resolution: {integrity: sha512-zRrDuZJzTevrrwElYosFztgldhqW6G9q5zOeQXfVQFkkEJCNfg36ixeiofKRU8uu2x+j+T6216mhMNB6HiuY+w==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@parcel/core': ^2.9.3 + dependencies: + '@parcel/core': 2.9.3 + '@parcel/diagnostic': 2.9.3 + '@parcel/logger': 2.9.3 + '@parcel/profiler': 2.9.3 + '@parcel/types': 2.9.3(@parcel/core@2.9.3) + '@parcel/utils': 2.9.3 + nullthrows: 1.1.1 + + /@playwright/test@1.37.0: + resolution: {integrity: sha512-181WBLk4SRUyH1Q96VZl7BP6HcK0b7lbdeKisn3N/vnjitk+9HbdlFz/L5fey05vxaAhldIDnzo8KUoy8S3mmQ==} + engines: {node: '>=16'} + hasBin: true + dependencies: + '@types/node': 18.17.5 + playwright-core: 1.37.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /@preconstruct/cli@2.8.1: + resolution: {integrity: sha512-PX5w+au06iY/QaT+9RLmRlIfavRCRoMTC/krwtNrgPEnubR9e6P+QlywrKmwiEvkzbR9AEzGnRZL8uNRDDMzrQ==} + hasBin: true + dependencies: + '@babel/code-frame': 7.22.10 + '@babel/core': 7.22.10 + '@babel/helper-module-imports': 7.22.5 + '@babel/runtime': 7.22.10 '@preconstruct/hook': 0.4.0 '@rollup/plugin-alias': 3.1.9(rollup@2.79.1) '@rollup/plugin-commonjs': 15.1.0(rollup@2.79.1) @@ -1509,28 +1995,28 @@ packages: chalk: 4.1.2 dataloader: 2.2.2 detect-indent: 6.1.0 - enquirer: 2.3.6 + enquirer: 2.4.1 estree-walker: 2.0.2 fast-deep-equal: 2.0.1 - fast-glob: 3.2.12 + fast-glob: 3.3.1 fs-extra: 9.1.0 is-ci: 2.0.0 is-reference: 1.2.1 jest-worker: 26.6.2 - magic-string: 0.30.0 + magic-string: 0.30.2 meow: 7.1.1 - ms: 2.1.2 + ms: 2.1.3 normalize-path: 3.0.0 npm-packlist: 2.2.2 p-limit: 3.1.0 parse-glob: 3.0.4 parse-json: 5.2.0 quick-lru: 5.1.1 - resolve: 1.22.2 + resolve: 1.22.4 resolve-from: 5.0.0 rollup: 2.79.1 - semver: 7.5.1 - terser: 5.17.3 + semver: 7.5.4 + terser: 5.19.2 v8-compile-cache: 2.3.0 zod: 3.21.4 transitivePeerDependencies: @@ -1540,9 +2026,9 @@ packages: /@preconstruct/hook@0.4.0: resolution: {integrity: sha512-a7mrlPTM3tAFJyz43qb4pPVpUx8j8TzZBFsNFqcKcE/sEakNXRlQAuCT4RGZRf9dQiiUnBahzSIWawU4rENl+Q==} dependencies: - '@babel/core': 7.21.8 - '@babel/plugin-transform-modules-commonjs': 7.21.5(@babel/core@7.21.8) - pirates: 4.0.5 + '@babel/core': 7.22.10 + '@babel/plugin-transform-modules-commonjs': 7.22.5(@babel/core@7.22.10) + pirates: 4.0.6 source-map-support: 0.5.21 transitivePeerDependencies: - supports-color @@ -1575,7 +2061,7 @@ packages: glob: 7.2.3 is-reference: 1.2.1 magic-string: 0.25.9 - resolve: 1.22.2 + resolve: 1.22.4 rollup: 2.79.1 dev: false @@ -1599,7 +2085,7 @@ packages: builtin-modules: 3.3.0 deepmerge: 4.3.1 is-module: 1.0.0 - resolve: 1.22.2 + resolve: 1.22.4 rollup: 2.79.1 dev: false @@ -1625,88 +2111,104 @@ packages: rollup: 2.79.1 dev: false - /@swc/core-darwin-arm64@1.3.60: - resolution: {integrity: sha512-oCDKWGdSO1WyErduGfiITRDoq7ZBt9PXETlhi8BGKH/wCc/3mfSNI9wXAg3Stn8mrT0lUJtdsnwMI/eZp6dK+A==} + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + + /@sinonjs/commons@3.0.0: + resolution: {integrity: sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==} + dependencies: + type-detect: 4.0.8 + dev: true + + /@sinonjs/fake-timers@10.3.0: + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + dependencies: + '@sinonjs/commons': 3.0.0 + dev: true + + /@swc/core-darwin-arm64@1.3.76: + resolution: {integrity: sha512-ovviEhZ/1E81Z9OGrO0ivLWk4VCa3I3ZzM+cd3gugglRRwVwtlIaoIYqY5S3KiCAupDd1+UCl5X7Vbio7a/V8g==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] requiresBuild: true optional: true - /@swc/core-darwin-x64@1.3.60: - resolution: {integrity: sha512-pcE/1oUlmN/BkKndOPtViqTkaM5pomagXATo+Muqn4QNMnkSOEVcmF9T3Lr3nB1A7O/fwCew3/aHwZ5B2TZ1tA==} + /@swc/core-darwin-x64@1.3.76: + resolution: {integrity: sha512-tcySTDqs0SHCebtW35sCdcLWsmTEo7bEwx0gNL/spetqVT9fpFi6qU8qcnt7i2KaZHbeNl9g1aadu+Yrni+GzA==} engines: {node: '>=10'} cpu: [x64] os: [darwin] requiresBuild: true optional: true - /@swc/core-linux-arm-gnueabihf@1.3.60: - resolution: {integrity: sha512-Moc+86SWcbPr06PaQYUb0Iwli425F7QgjwTCNEPYA6OYUsjaJhXMaHViW2WdGIXue2+eaQbg31BHQd14jXcoBg==} + /@swc/core-linux-arm-gnueabihf@1.3.76: + resolution: {integrity: sha512-apgzpGWy1AwoMF4urAAASsAjE7rEzZFIF+p6utuxhS7cNHzE0AyEVDYJbo+pzBdlZ8orBdzzsHtFwoEgKOjebA==} engines: {node: '>=10'} cpu: [arm] os: [linux] requiresBuild: true optional: true - /@swc/core-linux-arm64-gnu@1.3.60: - resolution: {integrity: sha512-pPGZrTgSXBvp6IrXPXz8UJr82AElf8hMuK4rNHmLGDCqrWnRIFLUpiAsc2WCFIgdwqitZNQoM+F2vbceA/bkKg==} + /@swc/core-linux-arm64-gnu@1.3.76: + resolution: {integrity: sha512-c3c0zz6S0eludqidDpuqbadE0WT3OZczyQxe9Vw8lFFXES85mvNGtwYzyGK2o7TICpsuHrndwDIoYpmpWk879g==} engines: {node: '>=10'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@swc/core-linux-arm64-musl@1.3.60: - resolution: {integrity: sha512-HSFQaVUkjWYNsQeymAQ3IPX3csRQvHe6MFyqPfvCCQ4dFlxPvlS7VvNaLnGG+ZW1ek7Lc+hEX+4NGzZKsxDIHA==} + /@swc/core-linux-arm64-musl@1.3.76: + resolution: {integrity: sha512-Is3bpq7F2qtlnkzEeOD6HIZJPpOmu3q6c82lKww90Q0NnrlSluVMozTHJgwVoFZyizH7uLnk0LuNcEAWLnmJIw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@swc/core-linux-x64-gnu@1.3.60: - resolution: {integrity: sha512-WJt/X6HHM3/TszckRA7UKMXec3FHYsB9xswQbIYxN4bfTQodu3Rc8bmpHYtFO7ScMLrhY+RljHLK6wclPvaEXw==} + /@swc/core-linux-x64-gnu@1.3.76: + resolution: {integrity: sha512-iwCeRzd9oSvUzqt7nU6p/ztceAWfnO9XVxBn502R5gs6QCBbE1HCKrWHDO77aKPK7ss+0NcIGHvXTd9L8/wRzw==} engines: {node: '>=10'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@swc/core-linux-x64-musl@1.3.60: - resolution: {integrity: sha512-DGGBqAPUXy/aPMBKokL3osZC9kM97HchiDPuprzwgTMP40YQ3hGCzNJ5jK7sOk9Tc4PEdZ2Igfr9sBHmCrxxQw==} + /@swc/core-linux-x64-musl@1.3.76: + resolution: {integrity: sha512-a671g4tW8kyFeuICsgq4uB9ukQfiIyXJT4V6YSnmqhCTz5mazWuDxZ5wKnx/1g5nXTl+U5cWH2TZaCJatp4GKA==} engines: {node: '>=10'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@swc/core-win32-arm64-msvc@1.3.60: - resolution: {integrity: sha512-wQg/BZPJvp5WpUbsBp7VHjhUh0DfYOPhP6dH67WO9QQ07+DvOk2DR2Bfh0z0ts1k7H/FsAqExWtTDCWMCRJiRQ==} + /@swc/core-win32-arm64-msvc@1.3.76: + resolution: {integrity: sha512-+swEFtjdMezS0vKUhJC3psdSDtOJGY5pEOt4e8XOPvn7aQpKQ9LfF49XVtIwDSk5SGuWtVoLFzkSY3reWUJCyg==} engines: {node: '>=10'} cpu: [arm64] os: [win32] requiresBuild: true optional: true - /@swc/core-win32-ia32-msvc@1.3.60: - resolution: {integrity: sha512-nqkd0XIVyGbnBwAxP4GIfx6n45/hAPETpmQYpDSGnucOKFJfvGdFGL81GDG1acPCq/oFtR3tIyTbPpKmJ0N6xQ==} + /@swc/core-win32-ia32-msvc@1.3.76: + resolution: {integrity: sha512-5CqwAykpGBJ3PqGLOlWGLGIPpBAG1IwWVDUfro3hhjQ7XJxV5Z1aQf5V5OJ90HJVtrEAVx2xx59UV/Dh081LOg==} engines: {node: '>=10'} cpu: [ia32] os: [win32] requiresBuild: true optional: true - /@swc/core-win32-x64-msvc@1.3.60: - resolution: {integrity: sha512-ouw+s22i9PYQpSE7Xc+ZittEyA87jElXABesviSpP+jgHt10sM5KFUpVAeV8DRlxJCXMJJ5AhOdCf4TAtFr+6A==} + /@swc/core-win32-x64-msvc@1.3.76: + resolution: {integrity: sha512-CiMpWLLlR3Cew9067E7XxaLBwYYJ90r9EhGSO6V1pvYSWj7ET/Ppmtj1ZhzPJMqRXAP6xflfl5R5o4ee1m4WLA==} engines: {node: '>=10'} cpu: [x64] os: [win32] requiresBuild: true optional: true - /@swc/core@1.3.60: - resolution: {integrity: sha512-dWfic7sVjnrStzGcMWakHd2XPau8UXGPmFUTkx6xGX+DOVtfAQVzG6ZW7ohw/yNcTqI05w6Ser26XMTMGBgXdA==} + /@swc/core@1.3.76: + resolution: {integrity: sha512-aYYTA2aVYkwJAZepQXtPnkUthhOfn8qd6rsh+lrJxonFrjmpI7RHt2tMDVTXP6XDX7fvnvrVtT1bwZfmBFPh0Q==} engines: {node: '>=10'} requiresBuild: true peerDependencies: @@ -1715,26 +2217,60 @@ packages: '@swc/helpers': optional: true optionalDependencies: - '@swc/core-darwin-arm64': 1.3.60 - '@swc/core-darwin-x64': 1.3.60 - '@swc/core-linux-arm-gnueabihf': 1.3.60 - '@swc/core-linux-arm64-gnu': 1.3.60 - '@swc/core-linux-arm64-musl': 1.3.60 - '@swc/core-linux-x64-gnu': 1.3.60 - '@swc/core-linux-x64-musl': 1.3.60 - '@swc/core-win32-arm64-msvc': 1.3.60 - '@swc/core-win32-ia32-msvc': 1.3.60 - '@swc/core-win32-x64-msvc': 1.3.60 + '@swc/core-darwin-arm64': 1.3.76 + '@swc/core-darwin-x64': 1.3.76 + '@swc/core-linux-arm-gnueabihf': 1.3.76 + '@swc/core-linux-arm64-gnu': 1.3.76 + '@swc/core-linux-arm64-musl': 1.3.76 + '@swc/core-linux-x64-gnu': 1.3.76 + '@swc/core-linux-x64-musl': 1.3.76 + '@swc/core-win32-arm64-msvc': 1.3.76 + '@swc/core-win32-ia32-msvc': 1.3.76 + '@swc/core-win32-x64-msvc': 1.3.76 /@swc/helpers@0.5.1: resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==} dependencies: - tslib: 2.5.0 + tslib: 2.6.1 + + /@tootallnate/once@2.0.0: + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + dev: true /@trysound/sax@0.2.0: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} + /@types/babel__core@7.20.1: + resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==} + dependencies: + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 + '@types/babel__generator': 7.6.4 + '@types/babel__template': 7.4.1 + '@types/babel__traverse': 7.20.1 + dev: true + + /@types/babel__generator@7.6.4: + resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@types/babel__template@7.4.1: + resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} + dependencies: + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 + dev: true + + /@types/babel__traverse@7.20.1: + resolution: {integrity: sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==} + dependencies: + '@babel/types': 7.22.10 + dev: true + /@types/estree@0.0.39: resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} dev: false @@ -1743,16 +2279,53 @@ packages: resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} dev: false - /@types/json-schema@7.0.11: - resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + /@types/graceful-fs@4.1.6: + resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} + dependencies: + '@types/node': 18.17.5 + dev: true + + /@types/istanbul-lib-coverage@2.0.4: + resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + dev: true + + /@types/istanbul-lib-report@3.0.0: + resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + dev: true + + /@types/istanbul-reports@3.0.1: + resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} + dependencies: + '@types/istanbul-lib-report': 3.0.0 + dev: true + + /@types/jest@29.5.3: + resolution: {integrity: sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==} + dependencies: + expect: 29.6.2 + pretty-format: 29.6.2 + dev: true + + /@types/jsdom@20.0.1: + resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} + dependencies: + '@types/node': 18.17.5 + '@types/tough-cookie': 4.0.2 + parse5: 7.1.2 + dev: true + + /@types/json-schema@7.0.12: + resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} dev: true /@types/minimist@1.2.2: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: false - /@types/node@18.16.9: - resolution: {integrity: sha512-IeB32oIV4oGArLrd7znD2rkHQ6EDCM+2Sr76dJnrHwv9OHBTTM6nuDLK9bmikXzPa0ZlWMWtRGo/Uw4mrzQedA==} + /@types/node@18.17.5: + resolution: {integrity: sha512-xNbS75FxH6P4UXTPUJp/zNPq6/xsfdJKussCWNOnz4aULWIRwMgP1LgaB5RiBnMX1DPCYenuqGZfnIAx5mbFLA==} /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -1762,14 +2335,14 @@ packages: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} dev: true - /@types/react-dom@18.2.4: - resolution: {integrity: sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==} + /@types/react-dom@18.2.7: + resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} dependencies: - '@types/react': 18.2.6 + '@types/react': 18.2.20 dev: true - /@types/react@18.2.6: - resolution: {integrity: sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==} + /@types/react@18.2.20: + resolution: {integrity: sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw==} dependencies: '@types/prop-types': 15.7.5 '@types/scheduler': 0.16.3 @@ -1779,7 +2352,7 @@ packages: /@types/resolve@1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@types/node': 18.16.9 + '@types/node': 18.17.5 dev: false /@types/scheduler@0.16.3: @@ -1790,8 +2363,26 @@ packages: resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} dev: true - /@typescript-eslint/eslint-plugin@5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@5.0.4): - resolution: {integrity: sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg==} + /@types/stack-utils@2.0.1: + resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} + dev: true + + /@types/tough-cookie@4.0.2: + resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==} + dev: true + + /@types/yargs-parser@21.0.0: + resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} + dev: true + + /@types/yargs@17.0.24: + resolution: {integrity: sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==} + dependencies: + '@types/yargs-parser': 21.0.0 + dev: true + + /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.47.0)(typescript@5.1.6): + resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -1801,25 +2392,25 @@ packages: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.5.1 - '@typescript-eslint/parser': 5.59.5(eslint@8.40.0)(typescript@5.0.4) - '@typescript-eslint/scope-manager': 5.59.5 - '@typescript-eslint/type-utils': 5.59.5(eslint@8.40.0)(typescript@5.0.4) - '@typescript-eslint/utils': 5.59.5(eslint@8.40.0)(typescript@5.0.4) + '@eslint-community/regexpp': 4.6.2 + '@typescript-eslint/parser': 5.62.0(eslint@8.47.0)(typescript@5.1.6) + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/type-utils': 5.62.0(eslint@8.47.0)(typescript@5.1.6) + '@typescript-eslint/utils': 5.62.0(eslint@8.47.0)(typescript@5.1.6) debug: 4.3.4 - eslint: 8.40.0 - grapheme-splitter: 1.0.4 + eslint: 8.47.0 + graphemer: 1.4.0 ignore: 5.2.4 natural-compare-lite: 1.4.0 - semver: 7.5.1 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + semver: 7.5.4 + tsutils: 3.21.0(typescript@5.1.6) + typescript: 5.1.6 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@5.59.5(eslint@8.40.0)(typescript@5.0.4): - resolution: {integrity: sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==} + /@typescript-eslint/parser@5.62.0(eslint@8.47.0)(typescript@5.1.6): + resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1828,26 +2419,26 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.59.5 - '@typescript-eslint/types': 5.59.5 - '@typescript-eslint/typescript-estree': 5.59.5(typescript@5.0.4) + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.1.6) debug: 4.3.4 - eslint: 8.40.0 - typescript: 5.0.4 + eslint: 8.47.0 + typescript: 5.1.6 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@5.59.5: - resolution: {integrity: sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==} + /@typescript-eslint/scope-manager@5.62.0: + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.59.5 - '@typescript-eslint/visitor-keys': 5.59.5 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 dev: true - /@typescript-eslint/type-utils@5.59.5(eslint@8.40.0)(typescript@5.0.4): - resolution: {integrity: sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==} + /@typescript-eslint/type-utils@5.62.0(eslint@8.47.0)(typescript@5.1.6): + resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -1856,23 +2447,23 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.59.5(typescript@5.0.4) - '@typescript-eslint/utils': 5.59.5(eslint@8.40.0)(typescript@5.0.4) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.1.6) + '@typescript-eslint/utils': 5.62.0(eslint@8.47.0)(typescript@5.1.6) debug: 4.3.4 - eslint: 8.40.0 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + eslint: 8.47.0 + tsutils: 3.21.0(typescript@5.1.6) + typescript: 5.1.6 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@5.59.5: - resolution: {integrity: sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==} + /@typescript-eslint/types@5.62.0: + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree@5.59.5(typescript@5.0.4): - resolution: {integrity: sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==} + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.1.6): + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -1880,49 +2471,68 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.59.5 - '@typescript-eslint/visitor-keys': 5.59.5 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.1 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + semver: 7.5.4 + tsutils: 3.21.0(typescript@5.1.6) + typescript: 5.1.6 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.59.5(eslint@8.40.0)(typescript@5.0.4): - resolution: {integrity: sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==} + /@typescript-eslint/utils@5.62.0(eslint@8.47.0)(typescript@5.1.6): + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0) - '@types/json-schema': 7.0.11 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) + '@types/json-schema': 7.0.12 '@types/semver': 7.5.0 - '@typescript-eslint/scope-manager': 5.59.5 - '@typescript-eslint/types': 5.59.5 - '@typescript-eslint/typescript-estree': 5.59.5(typescript@5.0.4) - eslint: 8.40.0 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.1.6) + eslint: 8.47.0 eslint-scope: 5.1.1 - semver: 7.5.1 + semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@5.59.5: - resolution: {integrity: sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==} + /@typescript-eslint/visitor-keys@5.62.0: + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.59.5 - eslint-visitor-keys: 3.4.1 + '@typescript-eslint/types': 5.62.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /abab@2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} dev: true /abortcontroller-polyfill@1.7.5: resolution: {integrity: sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==} + /acorn-globals@7.0.1: + resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} + dependencies: + acorn: 8.10.0 + acorn-walk: 8.2.0 + dev: true + + /acorn-jsx@5.3.2(acorn@8.10.0): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.10.0 + dev: true + /acorn-jsx@5.3.2(acorn@8.8.2): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1931,10 +2541,30 @@ packages: acorn: 8.8.2 dev: true + /acorn-walk@8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn@8.10.0: + resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} + engines: {node: '>=0.4.0'} + hasBin: true + /acorn@8.8.2: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} hasBin: true + dev: true + + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1950,10 +2580,16 @@ packages: engines: {node: '>=6'} dev: false + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + dev: true + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -1967,6 +2603,25 @@ packages: dependencies: color-convert: 2.0.1 + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + dev: true + /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1980,11 +2635,87 @@ packages: engines: {node: '>=0.10.0'} dev: false + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: true + /at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} dev: false + /babel-jest@29.6.2(@babel/core@7.22.10): + resolution: {integrity: sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + dependencies: + '@babel/core': 7.22.10 + '@jest/transform': 29.6.2 + '@types/babel__core': 7.20.1 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.5.0(@babel/core@7.22.10) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-jest-hoist@29.5.0: + resolution: {integrity: sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/template': 7.22.5 + '@babel/types': 7.22.10 + '@types/babel__core': 7.20.1 + '@types/babel__traverse': 7.20.1 + dev: true + + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.22.10): + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.22.10 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.10) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.22.10) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.10) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.22.10) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.10) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.10) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.10) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.10) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.10) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.10) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.10) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.10) + dev: true + + /babel-preset-jest@29.5.0(@babel/core@7.22.10): + resolution: {integrity: sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.22.10 + babel-plugin-jest-hoist: 29.5.0 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.10) + dev: true + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2008,19 +2739,31 @@ packages: dependencies: fill-range: 7.0.1 - /browserslist@4.21.5: - resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==} + /browserslist@4.21.10: + resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001487 - electron-to-chromium: 1.4.394 - node-releases: 2.0.10 - update-browserslist-db: 1.0.11(browserslist@4.21.5) + caniuse-lite: 1.0.30001520 + electron-to-chromium: 1.4.490 + node-releases: 2.0.13 + update-browserslist-db: 1.0.11(browserslist@4.21.10) + + /bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + dependencies: + fast-json-stable-stringify: 2.1.0 + dev: true + + /bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + dependencies: + node-int64: 0.4.0 + dev: true /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: false /builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} @@ -2043,10 +2786,14 @@ packages: /camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} - dev: false - /caniuse-lite@1.0.30001487: - resolution: {integrity: sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==} + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true + + /caniuse-lite@1.0.30001520: + resolution: {integrity: sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==} /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -2063,6 +2810,11 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 + /char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + dev: true + /chrome-trace-event@1.0.3: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} engines: {node: '>=6.0'} @@ -2071,10 +2823,37 @@ packages: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} dev: false + /ci-info@3.8.0: + resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} + engines: {node: '>=8'} + dev: true + + /cjs-module-lexer@1.2.3: + resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + dev: true + + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + /clone@2.1.2: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} engines: {node: '>=0.8'} + /co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + dev: true + + /collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + dev: true + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -2092,6 +2871,13 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: true + /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: false @@ -2110,8 +2896,12 @@ packages: /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - /cosmiconfig@8.1.3: - resolution: {integrity: sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==} + /convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + dev: true + + /cosmiconfig@8.2.0: + resolution: {integrity: sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==} engines: {node: '>=14'} dependencies: import-fresh: 3.3.0 @@ -2119,8 +2909,8 @@ packages: parse-json: 5.2.0 path-type: 4.0.0 - /crelt@1.0.5: - resolution: {integrity: sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==} + /crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} dev: false /cross-spawn@7.0.3: @@ -2158,10 +2948,34 @@ packages: dependencies: css-tree: 1.1.3 + /cssom@0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + dev: true + + /cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + dev: true + + /cssstyle@2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} + dependencies: + cssom: 0.3.8 + dev: true + /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} dev: true + /data-urls@3.0.2: + resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} + engines: {node: '>=12'} + dependencies: + abab: 2.0.6 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + dev: true + /dataloader@2.2.2: resolution: {integrity: sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==} dev: false @@ -2190,14 +3004,31 @@ packages: engines: {node: '>=0.10.0'} dev: false - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + /decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} dev: true - /deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - dev: false + /dedent@1.5.1: + resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + dev: true + + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: true /detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} @@ -2209,6 +3040,16 @@ packages: engines: {node: '>=0.10'} hasBin: true + /detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + dev: true + + /diff-sequences@29.4.3: + resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2233,6 +3074,13 @@ packages: /domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + /domexception@4.0.0: + resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} + engines: {node: '>=12'} + dependencies: + webidl-conversions: 7.0.0 + dev: true + /domhandler@4.3.1: resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} engines: {node: '>= 4'} @@ -2253,14 +3101,24 @@ packages: resolution: {integrity: sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==} engines: {node: '>=6'} - /electron-to-chromium@1.4.394: - resolution: {integrity: sha512-0IbC2cfr8w5LxTz+nmn2cJTGafsK9iauV2r5A5scfzyovqLrxuLoxOHE5OBobP3oVIggJT+0JfKnw9sm87c8Hw==} + /electron-to-chromium@1.4.490: + resolution: {integrity: sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A==} + + /emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + dev: true + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true - /enquirer@2.3.6: - resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} + /enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} dependencies: ansi-colors: 4.1.3 + strip-ansi: 6.0.1 dev: false /entities@2.2.0: @@ -2270,6 +3128,11 @@ packages: resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} engines: {node: '>=0.12'} + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -2283,11 +3146,28 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} + /escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + dev: true + /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} dev: true + /escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + dev: true + /eslint-plugin-no-restricted-imports@0.0.0: resolution: {integrity: sha512-sxHk6vmXheLagzJGKqq93u9sz+xXLyuXcW4ZCPrOMQ0PL365PyJyhaKRWYgMgPZs1YhBmhyopdqO62AlGz+jSQ==} engines: {node: '>=0.10.0'} @@ -2320,11 +3200,24 @@ packages: estraverse: 5.3.0 dev: true + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + /eslint-visitor-keys@3.4.1: resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /eslint@8.40.0: resolution: {integrity: sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2374,6 +3267,52 @@ packages: - supports-color dev: true + /eslint@8.47.0: + resolution: {integrity: sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) + '@eslint-community/regexpp': 4.6.2 + '@eslint/eslintrc': 2.1.2 + '@eslint/js': 8.47.0 + '@humanwhocodes/config-array': 0.11.10 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.21.0 + graphemer: 1.4.0 + ignore: 5.2.4 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /espree@9.5.2: resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2383,6 +3322,21 @@ packages: eslint-visitor-keys: 3.4.1 dev: true + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.10.0 + acorn-jsx: 5.3.2(acorn@8.10.0) + eslint-visitor-keys: 3.4.3 + dev: true + + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true + /esquery@1.5.0: resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} @@ -2420,6 +3374,38 @@ packages: engines: {node: '>=0.10.0'} dev: true + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true + + /exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + dev: true + + /expect@29.6.2: + resolution: {integrity: sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/expect-utils': 29.6.2 + '@types/node': 18.17.5 + jest-get-type: 29.4.3 + jest-matcher-utils: 29.6.2 + jest-message-util: 29.6.2 + jest-util: 29.6.2 + dev: true + /fast-deep-equal@2.0.1: resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} dev: false @@ -2428,8 +3414,8 @@ packages: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true - /fast-glob@3.2.12: - resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2451,6 +3437,12 @@ packages: dependencies: reusify: 1.0.4 + /fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + dependencies: + bser: 2.1.1 + dev: true + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2470,7 +3462,6 @@ packages: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - dev: false /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} @@ -2492,6 +3483,15 @@ packages: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: true + /fs-extra@9.1.0: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} @@ -2514,17 +3514,31 @@ packages: /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: false /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + + /get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + dev: true + /get-port@4.2.0: resolution: {integrity: sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==} engines: {node: '>=6'} dev: true + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: true + /get-them-args@1.3.2: resolution: {integrity: sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw==} dev: false @@ -2575,6 +3589,13 @@ packages: engines: {node: '>=8'} dependencies: type-fest: 0.20.2 + dev: true + + /globals@13.21.0: + resolution: {integrity: sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 /globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} @@ -2582,7 +3603,7 @@ packages: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.2.12 + fast-glob: 3.3.1 ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 @@ -2590,12 +3611,15 @@ packages: /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: false /grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true + /hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -2614,12 +3638,22 @@ packages: engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 - dev: false /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: false + /html-encoding-sniffer@3.0.0: + resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} + engines: {node: '>=12'} + dependencies: + whatwg-encoding: 2.0.0 + dev: true + + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true + /htmlnano@2.0.4(svgo@2.8.0): resolution: {integrity: sha512-WGCkyGFwjKW1GeCBsPYacMvaMnZtFJ0zIRnC2NCddkA+IOEhTqskXrS7lep+3yYZw/nQ3dW1UAX4yA/GJyR8BA==} peerDependencies: @@ -2649,7 +3683,7 @@ packages: uncss: optional: true dependencies: - cosmiconfig: 8.1.3 + cosmiconfig: 8.2.0 posthtml: 0.16.6 svgo: 2.8.0 timsort: 0.3.0 @@ -2662,6 +3696,39 @@ packages: domutils: 2.8.0 entities: 3.0.1 + /http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: true + + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: true + /ignore-walk@3.0.4: resolution: {integrity: sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==} dependencies: @@ -2684,6 +3751,15 @@ packages: parent-module: 1.0.1 resolve-from: 4.0.0 + /import-local@3.1.0: + resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + engines: {node: '>=8'} + hasBin: true + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + dev: true + /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -2713,11 +3789,10 @@ packages: ci-info: 2.0.0 dev: false - /is-core-module@2.12.0: - resolution: {integrity: sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==} + /is-core-module@2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} dependencies: has: 1.0.3 - dev: false /is-dotfile@1.0.3: resolution: {integrity: sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==} @@ -2733,6 +3808,16 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + dev: true + /is-glob@2.0.1: resolution: {integrity: sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==} engines: {node: '>=0.10.0'} @@ -2767,25 +3852,513 @@ packages: engines: {node: '>=0.10.0'} dev: false + /is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + dev: true + /is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} dependencies: '@types/estree': 1.0.1 dev: false + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true + /istanbul-lib-coverage@3.2.0: + resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} + engines: {node: '>=8'} + dev: true + + /istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + dependencies: + '@babel/core': 7.22.10 + '@babel/parser': 7.22.10 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + dependencies: + istanbul-lib-coverage: 3.2.0 + make-dir: 4.0.0 + supports-color: 7.2.0 + dev: true + + /istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + dependencies: + debug: 4.3.4 + istanbul-lib-coverage: 3.2.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-reports@3.1.6: + resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + dev: true + + /jest-changed-files@29.5.0: + resolution: {integrity: sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + execa: 5.1.1 + p-limit: 3.1.0 + dev: true + + /jest-circus@29.6.2: + resolution: {integrity: sha512-G9mN+KOYIUe2sB9kpJkO9Bk18J4dTDArNFPwoZ7WKHKel55eKIS/u2bLthxgojwlf9NLCVQfgzM/WsOVvoC6Fw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.6.2 + '@jest/expect': 29.6.2 + '@jest/test-result': 29.6.2 + '@jest/types': 29.6.1 + '@types/node': 18.17.5 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.1 + is-generator-fn: 2.1.0 + jest-each: 29.6.2 + jest-matcher-utils: 29.6.2 + jest-message-util: 29.6.2 + jest-runtime: 29.6.2 + jest-snapshot: 29.6.2 + jest-util: 29.6.2 + p-limit: 3.1.0 + pretty-format: 29.6.2 + pure-rand: 6.0.2 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + + /jest-cli@29.6.2(@types/node@18.17.5): + resolution: {integrity: sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.6.2 + '@jest/test-result': 29.6.2 + '@jest/types': 29.6.1 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + import-local: 3.1.0 + jest-config: 29.6.2(@types/node@18.17.5) + jest-util: 29.6.2 + jest-validate: 29.6.2 + prompts: 2.4.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + + /jest-config@29.6.2(@types/node@18.17.5): + resolution: {integrity: sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.22.10 + '@jest/test-sequencer': 29.6.2 + '@jest/types': 29.6.1 + '@types/node': 18.17.5 + babel-jest: 29.6.2(@babel/core@7.22.10) + chalk: 4.1.2 + ci-info: 3.8.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.6.2 + jest-environment-node: 29.6.2 + jest-get-type: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.6.2 + jest-runner: 29.6.2 + jest-util: 29.6.2 + jest-validate: 29.6.2 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.6.2 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + + /jest-diff@29.6.2: + resolution: {integrity: sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + diff-sequences: 29.4.3 + jest-get-type: 29.4.3 + pretty-format: 29.6.2 + dev: true + + /jest-docblock@29.4.3: + resolution: {integrity: sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + detect-newline: 3.1.0 + dev: true + + /jest-each@29.6.2: + resolution: {integrity: sha512-MsrsqA0Ia99cIpABBc3izS1ZYoYfhIy0NNWqPSE0YXbQjwchyt6B1HD2khzyPe1WiJA7hbxXy77ZoUQxn8UlSw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.1 + chalk: 4.1.2 + jest-get-type: 29.4.3 + jest-util: 29.6.2 + pretty-format: 29.6.2 + dev: true + + /jest-environment-jsdom@29.6.2: + resolution: {integrity: sha512-7oa/+266AAEgkzae8i1awNEfTfjwawWKLpiw2XesZmaoVVj9u9t8JOYx18cG29rbPNtkUlZ8V4b5Jb36y/VxoQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + dependencies: + '@jest/environment': 29.6.2 + '@jest/fake-timers': 29.6.2 + '@jest/types': 29.6.1 + '@types/jsdom': 20.0.1 + '@types/node': 18.17.5 + jest-mock: 29.6.2 + jest-util: 29.6.2 + jsdom: 20.0.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /jest-environment-node@29.6.2: + resolution: {integrity: sha512-YGdFeZ3T9a+/612c5mTQIllvWkddPbYcN2v95ZH24oWMbGA4GGS2XdIF92QMhUhvrjjuQWYgUGW2zawOyH63MQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.6.2 + '@jest/fake-timers': 29.6.2 + '@jest/types': 29.6.1 + '@types/node': 18.17.5 + jest-mock: 29.6.2 + jest-util: 29.6.2 + dev: true + + /jest-get-type@29.4.3: + resolution: {integrity: sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /jest-haste-map@29.6.2: + resolution: {integrity: sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.1 + '@types/graceful-fs': 4.1.6 + '@types/node': 18.17.5 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.4.3 + jest-util: 29.6.2 + jest-worker: 29.6.2 + micromatch: 4.0.5 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /jest-leak-detector@29.6.2: + resolution: {integrity: sha512-aNqYhfp5uYEO3tdWMb2bfWv6f0b4I0LOxVRpnRLAeque2uqOVVMLh6khnTcE2qJ5wAKop0HcreM1btoysD6bPQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.4.3 + pretty-format: 29.6.2 + dev: true + + /jest-matcher-utils@29.6.2: + resolution: {integrity: sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + jest-diff: 29.6.2 + jest-get-type: 29.4.3 + pretty-format: 29.6.2 + dev: true + + /jest-message-util@29.6.2: + resolution: {integrity: sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/code-frame': 7.22.10 + '@jest/types': 29.6.1 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.5 + pretty-format: 29.6.2 + slash: 3.0.0 + stack-utils: 2.0.6 + dev: true + + /jest-mock@29.6.2: + resolution: {integrity: sha512-hoSv3lb3byzdKfwqCuT6uTscan471GUECqgNYykg6ob0yiAw3zYc7OrPnI9Qv8Wwoa4lC7AZ9hyS4AiIx5U2zg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.1 + '@types/node': 18.17.5 + jest-util: 29.6.2 + dev: true + + /jest-pnp-resolver@1.2.3(jest-resolve@29.6.2): + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: + jest-resolve: 29.6.2 + dev: true + + /jest-regex-util@29.4.3: + resolution: {integrity: sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /jest-resolve-dependencies@29.6.2: + resolution: {integrity: sha512-LGqjDWxg2fuQQm7ypDxduLu/m4+4Lb4gczc13v51VMZbVP5tSBILqVx8qfWcsdP8f0G7aIqByIALDB0R93yL+w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-regex-util: 29.4.3 + jest-snapshot: 29.6.2 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-resolve@29.6.2: + resolution: {integrity: sha512-G/iQUvZWI5e3SMFssc4ug4dH0aZiZpsDq9o1PtXTV1210Ztyb2+w+ZgQkB3iOiC5SmAEzJBOHWz6Hvrd+QnNPw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.6.2 + jest-pnp-resolver: 1.2.3(jest-resolve@29.6.2) + jest-util: 29.6.2 + jest-validate: 29.6.2 + resolve: 1.22.4 + resolve.exports: 2.0.2 + slash: 3.0.0 + dev: true + + /jest-runner@29.6.2: + resolution: {integrity: sha512-wXOT/a0EspYgfMiYHxwGLPCZfC0c38MivAlb2lMEAlwHINKemrttu1uSbcGbfDV31sFaPWnWJPmb2qXM8pqZ4w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.6.2 + '@jest/environment': 29.6.2 + '@jest/test-result': 29.6.2 + '@jest/transform': 29.6.2 + '@jest/types': 29.6.1 + '@types/node': 18.17.5 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.4.3 + jest-environment-node: 29.6.2 + jest-haste-map: 29.6.2 + jest-leak-detector: 29.6.2 + jest-message-util: 29.6.2 + jest-resolve: 29.6.2 + jest-runtime: 29.6.2 + jest-util: 29.6.2 + jest-watcher: 29.6.2 + jest-worker: 29.6.2 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-runtime@29.6.2: + resolution: {integrity: sha512-2X9dqK768KufGJyIeLmIzToDmsN0m7Iek8QNxRSI/2+iPFYHF0jTwlO3ftn7gdKd98G/VQw9XJCk77rbTGZnJg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.6.2 + '@jest/fake-timers': 29.6.2 + '@jest/globals': 29.6.2 + '@jest/source-map': 29.6.0 + '@jest/test-result': 29.6.2 + '@jest/transform': 29.6.2 + '@jest/types': 29.6.1 + '@types/node': 18.17.5 + chalk: 4.1.2 + cjs-module-lexer: 1.2.3 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.6.2 + jest-message-util: 29.6.2 + jest-mock: 29.6.2 + jest-regex-util: 29.4.3 + jest-resolve: 29.6.2 + jest-snapshot: 29.6.2 + jest-util: 29.6.2 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-snapshot@29.6.2: + resolution: {integrity: sha512-1OdjqvqmRdGNvWXr/YZHuyhh5DeaLp1p/F8Tht/MrMw4Kr1Uu/j4lRG+iKl1DAqUJDWxtQBMk41Lnf/JETYBRA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/core': 7.22.10 + '@babel/generator': 7.22.10 + '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.10) + '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.22.10) + '@babel/types': 7.22.10 + '@jest/expect-utils': 29.6.2 + '@jest/transform': 29.6.2 + '@jest/types': 29.6.1 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.10) + chalk: 4.1.2 + expect: 29.6.2 + graceful-fs: 4.2.11 + jest-diff: 29.6.2 + jest-get-type: 29.4.3 + jest-matcher-utils: 29.6.2 + jest-message-util: 29.6.2 + jest-util: 29.6.2 + natural-compare: 1.4.0 + pretty-format: 29.6.2 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-util@29.6.2: + resolution: {integrity: sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.1 + '@types/node': 18.17.5 + chalk: 4.1.2 + ci-info: 3.8.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + dev: true + + /jest-validate@29.6.2: + resolution: {integrity: sha512-vGz0yMN5fUFRRbpJDPwxMpgSXW1LDKROHfBopAvDcmD6s+B/s8WJrwi+4bfH4SdInBA5C3P3BI19dBtKzx1Arg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.1 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.4.3 + leven: 3.1.0 + pretty-format: 29.6.2 + dev: true + + /jest-watcher@29.6.2: + resolution: {integrity: sha512-GZitlqkMkhkefjfN/p3SJjrDaxPflqxEAv3/ik10OirZqJGYH5rPiIsgVcfof0Tdqg3shQGdEIxDBx+B4tuLzA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.6.2 + '@jest/types': 29.6.1 + '@types/node': 18.17.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.6.2 + string-length: 4.0.2 + dev: true + /jest-worker@26.6.2: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.16.9 + '@types/node': 18.17.5 merge-stream: 2.0.0 supports-color: 7.2.0 dev: false + /jest-worker@29.6.2: + resolution: {integrity: sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@types/node': 18.17.5 + jest-util: 29.6.2 + merge-stream: 2.0.0 + supports-color: 8.1.1 + dev: true + + /jest@29.6.2(@types/node@18.17.5): + resolution: {integrity: sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.6.2 + '@jest/types': 29.6.1 + import-local: 3.1.0 + jest-cli: 29.6.2(@types/node@18.17.5) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /js-sdsl@4.4.0: resolution: {integrity: sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==} dev: true @@ -2793,11 +4366,60 @@ packages: /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + /js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + dev: true + /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true dependencies: - argparse: 2.0.1 + argparse: 2.0.1 + + /jsdom@20.0.3: + resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} + engines: {node: '>=14'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + dependencies: + abab: 2.0.6 + acorn: 8.10.0 + acorn-globals: 7.0.1 + cssom: 0.5.0 + cssstyle: 2.3.0 + data-urls: 3.0.2 + decimal.js: 10.4.3 + domexception: 4.0.0 + escodegen: 2.1.0 + form-data: 4.0.0 + html-encoding-sniffer: 3.0.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.7 + parse5: 7.1.2 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.3 + w3c-xmlserializer: 4.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 2.0.0 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + ws: 8.13.0 + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true /jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} @@ -2841,6 +4463,16 @@ packages: engines: {node: '>=0.10.0'} dev: false + /kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + dev: true + + /leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + dev: true + /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -2855,84 +4487,84 @@ packages: immediate: 3.0.6 dev: false - /lightningcss-darwin-arm64@1.20.0: - resolution: {integrity: sha512-aYEohJTlzwB8URJaNiS57tMbjyLub0mYvxlxKQk8SZv+irXx6MoBWpDNQKKTS9gg1pGf/eAwjpa3BLAoCBsh1A==} + /lightningcss-darwin-arm64@1.21.5: + resolution: {integrity: sha512-z05hyLX85WY0UfhkFUOrWEFqD69lpVAmgl3aDzMKlIZJGygbhbegqb4PV8qfUrKKNBauut/qVNPKZglhTaDDxA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] requiresBuild: true optional: true - /lightningcss-darwin-x64@1.20.0: - resolution: {integrity: sha512-cmMgY8FFWVaGgtift7eKKkHMqlz9O09/yTdlCXEDOeDP9yeo6vHOBTRP7ojb368kjw8Ew3l0L2uT1Gtx56eNkg==} + /lightningcss-darwin-x64@1.21.5: + resolution: {integrity: sha512-MSJhmej/U9MrdPxDk7+FWhO8+UqVoZUHG4VvKT5RQ4RJtqtANTiWiI97LvoVNMtdMnHaKs1Pkji6wHUFxjJsHQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] requiresBuild: true optional: true - /lightningcss-linux-arm-gnueabihf@1.20.0: - resolution: {integrity: sha512-/m+NDO1O6JCv7R9F0XWlXcintQHx4MPNU+kt8jZJO07LLdGwCfvjN31GVcwVPlStnnx/cU8uTTmax6g/Qu/whg==} + /lightningcss-linux-arm-gnueabihf@1.21.5: + resolution: {integrity: sha512-xN6+5/JsMrbZHL1lPl+MiNJ3Xza12ueBKPepiyDCFQzlhFRTj7D0LG+cfNTzPBTO8KcYQynLpl1iBB8LGp3Xtw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] requiresBuild: true optional: true - /lightningcss-linux-arm64-gnu@1.20.0: - resolution: {integrity: sha512-gtXoa6v0HvMRLbev6Hsef0+Q5He7NslB+Rs7G49Y5LUSdJeGIATEN+j8JzHC0DnxCsOGbEgGRmvtJzzYDkkluw==} + /lightningcss-linux-arm64-gnu@1.21.5: + resolution: {integrity: sha512-KfzFNhC4XTbmG3ma/xcTs/IhCwieW89XALIusKmnV0N618ZDXEB0XjWOYQRCXeK9mfqPdbTBpurEHV/XZtkniQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /lightningcss-linux-arm64-musl@1.20.0: - resolution: {integrity: sha512-Po7XpucM1kZnkiyd2BNwTExSDcZ8jm8uB9u+Sq44qjpkf5f75jreQwn3DQm9I1t5C6tB9HGt30HExMju9umJBQ==} + /lightningcss-linux-arm64-musl@1.21.5: + resolution: {integrity: sha512-bc0GytQO5Mn9QM6szaZ+31fQHNdidgpM1sSCwzPItz8hg3wOvKl8039rU0veMJV3ZgC9z0ypNRceLrSHeRHmXw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /lightningcss-linux-x64-gnu@1.20.0: - resolution: {integrity: sha512-8yR/fGNn/P0I+Lc3PK+VWPET/zdSpBfHFIG0DJ38TywMbItVKvnFvoTBwnIm4LqBz7g2G2dDexnNP95za2Ll8g==} + /lightningcss-linux-x64-gnu@1.21.5: + resolution: {integrity: sha512-JwMbgypPQgc2kW2av3OwzZ8cbrEuIiDiXPJdXRE6aVxu67yHauJawQLqJKTGUhiAhy6iLDG8Wg0a3/ziL+m+Kw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /lightningcss-linux-x64-musl@1.20.0: - resolution: {integrity: sha512-EmpJ+VkPZ8RACiB4m+l8TmapmE1W2UvJKDHE+ML/3Ihr9tRKUs3CibfnQTFZC8aSsrxgXagDAN+PgCDDhIyriA==} + /lightningcss-linux-x64-musl@1.21.5: + resolution: {integrity: sha512-Ib8b6IQ/OR/VrPU6YBgy4T3QnuHY7DUa95O+nz+cwrTkMSN6fuHcTcIaz4t8TJ6HI5pl3uxUOZjmtls2pyQWow==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /lightningcss-win32-x64-msvc@1.20.0: - resolution: {integrity: sha512-BRdPvbq7Cc1qxAzp2emqWJHrqsEkf4ggxS29VOnxT7jhkdHKU+a26OVMjvm/OL0NH0ToNOZNAPvHMSexiEgBeA==} + /lightningcss-win32-x64-msvc@1.21.5: + resolution: {integrity: sha512-A8cSi8lUpBeVmoF+DqqW7cd0FemDbCuKr490IXdjyeI+KL8adpSKUs8tcqO0OXPh1EoDqK7JNkD/dELmd4Iz5g==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] requiresBuild: true optional: true - /lightningcss@1.20.0: - resolution: {integrity: sha512-4bj8aP+Vi+or8Gwq/hknmicr4PmA8D9uL/3qY0N0daX5vYBMYERGI6Y93nzoeRgQMULq+gtrN/FvJYtH0xNN8g==} + /lightningcss@1.21.5: + resolution: {integrity: sha512-/pEUPeih2EwIx9n4T82aOG6CInN83tl/mWlw6B5gWLf36UplQi1L+5p3FUHsdt4fXVfOkkh9KIaM3owoq7ss8A==} engines: {node: '>= 12.0.0'} dependencies: detect-libc: 1.0.3 optionalDependencies: - lightningcss-darwin-arm64: 1.20.0 - lightningcss-darwin-x64: 1.20.0 - lightningcss-linux-arm-gnueabihf: 1.20.0 - lightningcss-linux-arm64-gnu: 1.20.0 - lightningcss-linux-arm64-musl: 1.20.0 - lightningcss-linux-x64-gnu: 1.20.0 - lightningcss-linux-x64-musl: 1.20.0 - lightningcss-win32-x64-msvc: 1.20.0 + lightningcss-darwin-arm64: 1.21.5 + lightningcss-darwin-x64: 1.21.5 + lightningcss-linux-arm-gnueabihf: 1.21.5 + lightningcss-linux-arm64-gnu: 1.21.5 + lightningcss-linux-arm64-musl: 1.21.5 + lightningcss-linux-x64-gnu: 1.21.5 + lightningcss-linux-x64-musl: 1.21.5 + lightningcss-win32-x64-msvc: 1.21.5 /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -2945,7 +4577,7 @@ packages: msgpackr: 1.8.5 node-addon-api: 4.3.0 node-gyp-build-optional-packages: 5.0.6 - ordered-binary: 1.4.0 + ordered-binary: 1.4.1 weak-lru-cache: 1.2.2 optionalDependencies: '@lmdb/lmdb-darwin-arm64': 2.7.11 @@ -2966,7 +4598,6 @@ packages: engines: {node: '>=8'} dependencies: p-locate: 4.1.0 - dev: false /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} @@ -2975,6 +4606,10 @@ packages: p-locate: 5.0.0 dev: true + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -3002,13 +4637,30 @@ packages: sourcemap-codec: 1.4.8 dev: false - /magic-string@0.30.0: - resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==} + /magic-string@0.30.2: + resolution: {integrity: sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==} engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 dev: false + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: true + + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + dependencies: + tmpl: 1.0.5 + dev: true + /map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} @@ -3022,7 +4674,7 @@ packages: /match-sorter@6.3.1: resolution: {integrity: sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==} dependencies: - '@babel/runtime': 7.21.5 + '@babel/runtime': 7.22.10 remove-accents: 0.4.2 dev: false @@ -3048,7 +4700,6 @@ packages: /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: false /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} @@ -3061,6 +4712,23 @@ packages: braces: 3.0.2 picomatch: 2.3.1 + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: true + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: true + + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true + /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -3083,6 +4751,10 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false + /msgpackr-extract@3.0.2: resolution: {integrity: sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==} hasBin: true @@ -3103,8 +4775,8 @@ packages: optionalDependencies: msgpackr-extract: 3.0.2 - /msgpackr@1.9.1: - resolution: {integrity: sha512-jJdrNH8tzfCtT0rjPFryBXjRDQE7rqfLkah4/8B4gYa7NNZYFBcGxqWBtfQpGC+oYyBwlkj3fARk4aooKNPHxg==} + /msgpackr@1.9.7: + resolution: {integrity: sha512-baUNaLvKQvVhzfWTNO07njwbZK1Lxjtb0P1JL6/EhXdLTHzR57/mZqqJC39TtQKvOmkJA4pcejS4dbk7BDgLLA==} optionalDependencies: msgpackr-extract: 3.0.2 @@ -3116,12 +4788,12 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true - /node-addon-api@3.2.1: - resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} - /node-addon-api@4.3.0: resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} + /node-addon-api@7.0.0: + resolution: {integrity: sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==} + /node-gyp-build-optional-packages@5.0.6: resolution: {integrity: sha512-2ZJErHG4du9G3/8IWl/l9Bp5BBFy63rno5GVmjQijvTuUZKsl6g8RB4KH/x3NLcV5ZBb4GsXmAuTYr6dRml3Gw==} hasBin: true @@ -3129,28 +4801,28 @@ packages: /node-gyp-build-optional-packages@5.0.7: resolution: {integrity: sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==} hasBin: true + requiresBuild: true optional: true - /node-gyp-build@4.6.0: - resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} - hasBin: true + /node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + dev: true - /node-releases@2.0.10: - resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} + /node-releases@2.0.13: + resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.2 - semver: 5.7.1 + resolve: 1.22.4 + semver: 5.7.2 validate-npm-package-license: 3.0.4 dev: false /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - dev: false /npm-bundled@1.1.2: resolution: {integrity: sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==} @@ -3173,6 +4845,13 @@ packages: npm-normalize-package-bin: 1.0.1 dev: false + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + dev: true + /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: @@ -3181,6 +4860,10 @@ packages: /nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + /nwsapi@2.2.7: + resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} + dev: true + /object-path@0.6.0: resolution: {integrity: sha512-fxrwsCFi3/p+LeLOAwo/wyRMODZxdGBtUlWRzsEpsUVrisZbEfZ21arxLGfaWfcnqb8oHPNihIb4XPE8CQPN5A==} engines: {node: '>=0.8.0'} @@ -3191,6 +4874,13 @@ packages: dependencies: wrappy: 1.0.2 + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: true + /optionator@0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} @@ -3203,15 +4893,26 @@ packages: word-wrap: 1.2.3 dev: true - /ordered-binary@1.4.0: - resolution: {integrity: sha512-EHQ/jk4/a9hLupIKxTfUsQRej1Yd/0QLQs3vGvIqg5ZtCYSzNhkzHoZc7Zf4e4kUlDaC3Uw8Q/1opOLNN2OKRQ==} + /optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /ordered-binary@1.4.1: + resolution: {integrity: sha512-9LtiGlPy982CsgxZvJGNNp2/NnrgEr6EAyN3iIEP3/8vd3YLgAZQHbQ75ZrkfBRGrNg37Dk3U6tuVb+B4Xfslg==} /p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} dependencies: p-try: 2.2.0 - dev: false /p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} @@ -3224,7 +4925,6 @@ packages: engines: {node: '>=8'} dependencies: p-limit: 2.3.0 - dev: false /p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} @@ -3236,7 +4936,6 @@ packages: /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - dev: false /parcel@2.9.3: resolution: {integrity: sha512-2GTVocFkwblV/TIg9AmT7TI2fO4xdWkyN8aFUEVtiVNWt96GTR3FgQyHFValfCbcj1k9Xf962Ws2hYXYUr9k1Q==} @@ -3291,11 +4990,17 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.21.4 + '@babel/code-frame': 7.22.10 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.5.0 + dev: true + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3311,7 +5016,6 @@ packages: /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: false /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -3324,13 +5028,19 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - /pirates@4.0.5: - resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - dev: false - /playwright-core@1.35.0: - resolution: {integrity: sha512-muMXyPmIx/2DPrCHOD1H1ePT01o7OdKxKj2ebmCAYvqhUy+Y1bpal7B0rdoxros7YrXI294JT/DWw2LqyiqTPA==} + /pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + dev: true + + /playwright-core@1.37.0: + resolution: {integrity: sha512-1c46jhTH/myQw6sesrcuHVtLoSNfJv8Pfy9t3rs6subY7kARv0HRw5PpyfPYPpPtQvBOmgbE6K+qgYUpj81LAA==} engines: {node: '>=16'} hasBin: true dev: true @@ -3368,22 +5078,51 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prettier@3.0.0: - resolution: {integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==} + /prettier@3.0.1: + resolution: {integrity: sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==} engines: {node: '>=14'} hasBin: true dev: true + /pretty-format@29.6.2: + resolution: {integrity: sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.0 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + /process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} dev: true + /prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + dev: true + + /psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + dev: true + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} dev: true + /pure-rand@6.0.2: + resolution: {integrity: sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==} + dev: true + + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: true + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -3409,6 +5148,10 @@ packages: /react-error-overlay@6.0.9: resolution: {integrity: sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==} + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + /react-refresh@0.9.0: resolution: {integrity: sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==} engines: {node: '>=0.10.0'} @@ -3482,15 +5225,35 @@ packages: /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + /regenerator-runtime@0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + dev: false + /remove-accents@0.4.2: resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==} dev: false + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + /requireindex@1.1.0: resolution: {integrity: sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==} engines: {node: '>=0.10.5'} dev: true + /requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + dev: true + + /resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + dependencies: + resolve-from: 5.0.0 + dev: true + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3498,16 +5261,19 @@ packages: /resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - dev: false - /resolve@1.22.2: - resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} + /resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} + dev: true + + /resolve@1.22.4: + resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} hasBin: true dependencies: - is-core-module: 2.12.0 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: false /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} @@ -3536,29 +5302,33 @@ packages: /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: true + + /saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + dependencies: + xmlchars: 2.2.0 + dev: true + /scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: loose-envify: 1.4.0 - /semver@5.7.1: - resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + /semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true dev: false - /semver@6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - /semver@7.5.1: - resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - - /semver@7.5.3: - resolution: {integrity: sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==} + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} hasBin: true dependencies: @@ -3580,6 +5350,14 @@ packages: resolution: {integrity: sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==} dev: false + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true + + /sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: true + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -3590,6 +5368,13 @@ packages: object-path: 0.6.0 dev: false + /source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + /source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: @@ -3628,6 +5413,10 @@ packages: resolution: {integrity: sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==} dev: false + /sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: true + /srcset@4.0.0: resolution: {integrity: sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==} engines: {node: '>=12'} @@ -3636,11 +5425,44 @@ packages: resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + /stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + dependencies: + escape-string-regexp: 2.0.0 + dev: true + + /string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + dev: true + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 + + /strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + dev: true + + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} dev: true /strip-indent@3.0.0: @@ -3671,10 +5493,16 @@ packages: dependencies: has-flag: 4.0.0 + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + dev: true + /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - dev: false /suspense@0.0.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Uw3qKyglDL/HyedUeo+U/szNmmzxQI0tRZL2VTf60rSqNTQwLwOoXGr052FilNaIEGDXjTswAqSMslaFIeEdzA==} @@ -3699,22 +5527,35 @@ packages: picocolors: 1.0.0 stable: 0.1.8 + /symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + dev: true + /term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} dev: true - /terser@5.17.3: - resolution: {integrity: sha512-AudpAZKmZHkG9jueayypz4duuCFJMMNGRMwaPvQKWfxKedh8Z2x3OCoDqIIi1xx5+iwx1u6Au8XQcc9Lke65Yg==} + /terser@5.19.2: + resolution: {integrity: sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==} engines: {node: '>=10'} hasBin: true dependencies: - '@jridgewell/source-map': 0.3.3 - acorn: 8.8.2 + '@jridgewell/source-map': 0.3.5 + acorn: 8.10.0 commander: 2.20.3 source-map-support: 0.5.21 dev: false + /test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + dev: true + /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true @@ -3722,6 +5563,10 @@ packages: /timsort@0.3.0: resolution: {integrity: sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==} + /tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + dev: true + /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -3732,26 +5577,77 @@ packages: dependencies: is-number: 7.0.0 + /tough-cookie@4.1.3: + resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} + engines: {node: '>=6'} + dependencies: + psl: 1.9.0 + punycode: 2.3.0 + universalify: 0.2.0 + url-parse: 1.5.10 + dev: true + + /tr46@3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} + dependencies: + punycode: 2.3.0 + dev: true + /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} dev: false + /ts-jest@29.1.1(@babel/core@7.22.10)(jest@29.6.2)(typescript@5.1.6): + resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.22.10 + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.6.2(@types/node@18.17.5) + jest-util: 29.6.2 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.5.4 + typescript: 5.1.6 + yargs-parser: 21.1.1 + dev: true + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true - /tslib@2.5.0: - resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} + /tslib@2.6.1: + resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==} - /tsutils@3.21.0(typescript@5.0.4): + /tsutils@3.21.0(typescript@5.1.6): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.0.4 + typescript: 5.1.6 dev: true /type-check@0.4.0: @@ -3761,6 +5657,11 @@ packages: prelude-ls: 1.2.1 dev: true + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + /type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} @@ -3770,6 +5671,11 @@ packages: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true + /type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} @@ -3780,23 +5686,28 @@ packages: engines: {node: '>=8'} dev: false - /typescript@5.0.4: - resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} - engines: {node: '>=12.20'} + /typescript@5.1.6: + resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} + engines: {node: '>=14.17'} hasBin: true + /universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + dev: true + /universalify@2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} dev: false - /update-browserslist-db@1.0.11(browserslist@4.21.5): + /update-browserslist-db@1.0.11(browserslist@4.21.10): resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.21.5 + browserslist: 4.21.10 escalade: 3.1.1 picocolors: 1.0.0 @@ -3806,6 +5717,13 @@ packages: punycode: 2.3.0 dev: true + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + dev: true + /utility-types@3.10.0: resolution: {integrity: sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==} engines: {node: '>= 4'} @@ -3814,6 +5732,15 @@ packages: resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} dev: false + /v8-to-istanbul@9.1.0: + resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} + engines: {node: '>=10.12.0'} + dependencies: + '@jridgewell/trace-mapping': 0.3.19 + '@types/istanbul-lib-coverage': 2.0.4 + convert-source-map: 1.9.0 + dev: true + /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: @@ -3821,13 +5748,51 @@ packages: spdx-expression-parse: 3.0.1 dev: false - /w3c-keyname@2.2.6: - resolution: {integrity: sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==} + /w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} dev: false + /w3c-xmlserializer@4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} + dependencies: + xml-name-validator: 4.0.0 + dev: true + + /walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + dependencies: + makeerror: 1.0.12 + dev: true + /weak-lru-cache@1.2.2: resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==} + /webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + dev: true + + /whatwg-encoding@2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} + dependencies: + iconv-lite: 0.6.3 + dev: true + + /whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + dev: true + + /whatwg-url@11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 + dev: true + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -3841,12 +5806,56 @@ packages: engines: {node: '>=0.10.0'} dev: true + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + /write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + dev: true + + /ws@8.13.0: + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + + /xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + dev: true + + /xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + dev: true + /xxhash-wasm@0.4.2: resolution: {integrity: sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA==} + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + /yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -3861,6 +5870,24 @@ packages: decamelize: 1.2.0 dev: false + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: true + + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true + /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} From d2246962551b6a1f4c4d6ce39ab2b2ae2dd60586 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 14 Aug 2023 14:17:36 -0400 Subject: [PATCH 02/35] Add Jest --- .github/workflows/jest.yml | 17 +++++++++++++++++ packages/react-resizable-panels/jest.config.js | 8 ++++++++ packages/react-resizable-panels/package.json | 2 ++ tsconfig.json | 3 ++- 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/jest.yml create mode 100644 packages/react-resizable-panels/jest.config.js diff --git a/.github/workflows/jest.yml b/.github/workflows/jest.yml new file mode 100644 index 000000000..ffe533a97 --- /dev/null +++ b/.github/workflows/jest.yml @@ -0,0 +1,17 @@ +name: "Jest" +on: [pull_request] +jobs: + tests-e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - uses: pnpm/action-setup@v2 + with: + version: 7 + - name: Install dependencies + run: pnpm install -r + - name: Build NPM packages + run: pnpm run prerelease + - name: Run tests + run: cd packages/react-resizable-panels && pnpm run test \ No newline at end of file diff --git a/packages/react-resizable-panels/jest.config.js b/packages/react-resizable-panels/jest.config.js new file mode 100644 index 000000000..ea6df48e4 --- /dev/null +++ b/packages/react-resizable-panels/jest.config.js @@ -0,0 +1,8 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: "ts-jest", + testEnvironmentOptions: { + customExportConditions: ["development"], + }, + testMatch: ["**/*.test.{ts,tsx}"], +}; diff --git a/packages/react-resizable-panels/package.json b/packages/react-resizable-panels/package.json index 3c0b1e543..a2bbb8c40 100644 --- a/packages/react-resizable-panels/package.json +++ b/packages/react-resizable-panels/package.json @@ -62,6 +62,8 @@ "types": "dist/react-resizable-panels.cjs.d.ts", "scripts": { "lint": "eslint \"src/**/*.{ts,tsx}\"", + "test": "jest --config=jest.config.js", + "test:watch": "jest --config=jest.config.js --watch", "watch": "parcel watch --port=2345" }, "devDependencies": { diff --git a/tsconfig.json b/tsconfig.json index 3e687ea4a..57d5168e2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ "moduleResolution": "bundler", "noImplicitAny": true, "strict": true, - "types": ["node"] + "typeRoots": ["node_modules/@types"], + "types": ["jest", "node"] }, "exclude": ["node_modules"], "include": ["declaration.d.ts", "packages/**/*.ts", "packages/**/*.tsx"] From 2cda40ed1ad28f36ddfb6fa43c0caedb4a766041 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 14 Aug 2023 14:20:03 -0400 Subject: [PATCH 03/35] Stashing in-progress changes --- .../src/utils/new/adjustLayoutByDelta.test.ts | 1514 +++++++++++++++++ .../src/utils/new/adjustLayoutByDelta.ts | 181 ++ .../src/utils/new/compareLayouts.test.ts | 9 + .../src/utils/new/compareLayouts.ts | 12 + .../new/computePercentagePanelConstraints.ts | 109 ++ .../new/convertPercentageToPixels.test.ts | 9 + .../utils/new/convertPercentageToPixels.ts | 6 + .../new/convertPixelsToPercentage.test.ts | 9 + .../utils/new/convertPixelsToPercentage.ts | 6 + .../src/utils/new/fuzzyCompareNumbers.ts | 7 + .../src/utils/new/resizePanel.ts | 40 + .../src/utils/new/test-utils.ts | 53 + .../src/utils/new/types.ts | 11 + .../new/validatePanelConstraints.test.ts | 115 ++ .../src/utils/new/validatePanelConstraints.ts | 88 + .../new/validatePanelGroupLayout.test.ts | 228 +++ .../src/utils/new/validatePanelGroupLayout.ts | 81 + 17 files changed, 2478 insertions(+) create mode 100644 packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.test.ts create mode 100644 packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.ts create mode 100644 packages/react-resizable-panels/src/utils/new/compareLayouts.test.ts create mode 100644 packages/react-resizable-panels/src/utils/new/compareLayouts.ts create mode 100644 packages/react-resizable-panels/src/utils/new/computePercentagePanelConstraints.ts create mode 100644 packages/react-resizable-panels/src/utils/new/convertPercentageToPixels.test.ts create mode 100644 packages/react-resizable-panels/src/utils/new/convertPercentageToPixels.ts create mode 100644 packages/react-resizable-panels/src/utils/new/convertPixelsToPercentage.test.ts create mode 100644 packages/react-resizable-panels/src/utils/new/convertPixelsToPercentage.ts create mode 100644 packages/react-resizable-panels/src/utils/new/fuzzyCompareNumbers.ts create mode 100644 packages/react-resizable-panels/src/utils/new/resizePanel.ts create mode 100644 packages/react-resizable-panels/src/utils/new/test-utils.ts create mode 100644 packages/react-resizable-panels/src/utils/new/types.ts create mode 100644 packages/react-resizable-panels/src/utils/new/validatePanelConstraints.test.ts create mode 100644 packages/react-resizable-panels/src/utils/new/validatePanelConstraints.ts create mode 100644 packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.test.ts create mode 100644 packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.ts diff --git a/packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.test.ts b/packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.test.ts new file mode 100644 index 000000000..7f6d39291 --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.test.ts @@ -0,0 +1,1514 @@ +import { adjustLayoutByDelta } from "./adjustLayoutByDelta"; + +describe("adjustLayoutByDelta", () => { + it("[1++,2]", () => { + expect( + adjustLayoutByDelta({ + delta: 1, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [{}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([51, 49]); + }); + + it("[1++,2]", () => { + expect( + adjustLayoutByDelta({ + delta: 25, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [{}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([75, 25]); + expect( + adjustLayoutByDelta({ + delta: 50, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [{}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([100, 0]); + }); + + it("[1++,2]", () => { + expect( + adjustLayoutByDelta({ + delta: 50, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [ + { + minSizePercentage: 20, + maxSizePercentage: 60, + }, + { + minSizePercentage: 10, + maxSizePercentage: 90, + }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([60, 40]); + }); + + it("[1++,2]", () => { + expect( + adjustLayoutByDelta({ + delta: 25, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [ + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 25, + }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([75, 25]); + }); + + it("[1++,2]", () => { + expect( + adjustLayoutByDelta({ + delta: 30, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [ + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 25, + }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([95, 5]); + }); + + it("[1++,2]", () => { + expect( + adjustLayoutByDelta({ + delta: 5, + groupSizePixels: NaN, + layout: [10, 90], + panelConstraints: [ + { + collapsedSizePercentage: 10, + collapsible: true, + minSizePercentage: 25, + }, + {}, + ], + pivotIndices: [0, 1], + }) + ).toEqual([25, 75]); + }); + + it("[1--,2]", () => { + expect( + adjustLayoutByDelta({ + delta: -1, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [{}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([49, 51]); + }); + + it("[1--,2]", () => { + expect( + adjustLayoutByDelta({ + delta: -25, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [{}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([25, 75]); + }); + + it("[1--,2]", () => { + expect( + adjustLayoutByDelta({ + delta: -50, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [{}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([0, 100]); + }); + + it("[1--,2]", () => { + expect( + adjustLayoutByDelta({ + delta: -50, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [ + { + minSizePercentage: 20, + maxSizePercentage: 60, + }, + { + minSizePercentage: 10, + maxSizePercentage: 90, + }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([20, 80]); + }); + + it("[1--,2]", () => { + expect( + adjustLayoutByDelta({ + delta: -25, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 25, + }, + {}, + ], + pivotIndices: [0, 1], + }) + ).toEqual([25, 75]); + }); + + it("[1--,2]", () => { + expect( + adjustLayoutByDelta({ + delta: -30, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 25, + }, + {}, + ], + pivotIndices: [0, 1], + }) + ).toEqual([5, 95]); + }); + + it("[1--,2]", () => { + // Edge case + // The second panel should prevent the first panel from collapsing + expect( + adjustLayoutByDelta({ + delta: -30, + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 25, + }, + { maxSizePercentage: 80 }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([25, 75]); + }); + + it("[1++,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 1, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([26, 49, 25]); + }); + + it("[1++,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 25, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([50, 25, 25]); + }); + + it("[1++,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 50, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([75, 0, 25]); + }); + + it("[1++,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 75, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([100, 0, 0]); + }); + + it("[1++,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 25, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [ + { maxSizePercentage: 35 }, + { minSizePercentage: 25 }, + {}, + ], + pivotIndices: [0, 1], + }) + ).toEqual([35, 40, 25]); + }); + + it("[1++,2,3]", () => { + // Any further than the max size should stop the drag/keyboard resize + expect( + adjustLayoutByDelta({ + delta: 25, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [ + { maxSizePercentage: 35 }, + { minSizePercentage: 25 }, + {}, + ], + pivotIndices: [0, 1], + }) + ).toEqual([35, 40, 25]); + }); + + it("[1++,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 5, + groupSizePixels: NaN, + layout: [25, 40, 35], + panelConstraints: [ + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 25, + }, + { minSizePercentage: 25 }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([30, 35, 35]); + }); + + it("[1++,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 26, + groupSizePixels: NaN, + layout: [25, 40, 35], + panelConstraints: [ + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 25, + }, + { minSizePercentage: 25 }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([60, 5, 35]); + }); + + it("[1++,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 80, + groupSizePixels: NaN, + layout: [25, 40, 35], + panelConstraints: [ + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 25, + }, + { minSizePercentage: 25 }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([70, 5, 25]); + }); + + it("[1--,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -1, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([24, 51, 25]); + }); + + it("[1--,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -25, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([0, 75, 25]); + }); + + it("[1--,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -1, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{ minSizePercentage: 20 }, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([24, 51, 25]); + }); + + it("[1--,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -10, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{ minSizePercentage: 20 }, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([20, 55, 25]); + }); + + it("[1--,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -5, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [ + { + // Implied min size 10 + }, + { maxSizePercentage: 70 }, + { maxSizePercentage: 20 }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([20, 55, 25]); + }); + + it("[1--,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -20, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [ + { + // Implied min size 10 + }, + { maxSizePercentage: 70 }, + { maxSizePercentage: 20 }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([10, 65, 25]); + }); + + it("[1--,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -10, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePixels: 15, + }, + {}, + {}, + ], + pivotIndices: [0, 1], + }) + ).toEqual([15, 60, 25]); + }); + + it("[1--,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -20, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePixels: 15, + }, + {}, + {}, + ], + pivotIndices: [0, 1], + }) + ).toEqual([5, 70, 25]); + }); + + it("[1--,2,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -20, + groupSizePixels: NaN, + layout: [45, 50, 5], + panelConstraints: [ + {}, + { + maxSizePercentage: 50, + }, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePixels: 15, + }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([45, 50, 5]); + }); + + it("[1,2++,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -1, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([25, 49, 26]); + }); + + it("[1,2++,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -25, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([25, 25, 50]); + }); + + it("[1,2++,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -50, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([25, 0, 75]); + }); + + it("[1,2++,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -75, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([0, 0, 100]); + }); + + it("[1,2++,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 5, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, { minSizePercentage: 15 }], + pivotIndices: [1, 2], + }) + ).toEqual([25, 55, 20]); + }); + + it("[1,2++,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 20, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, { minSizePercentage: 15 }], + pivotIndices: [1, 2], + }) + ).toEqual([25, 60, 15]); + }); + + it("[1,2++,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 5, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [ + {}, + {}, + { collapsible: true, minSizePercentage: 20 }, + ], + pivotIndices: [1, 2], + }) + ).toEqual([25, 55, 20]); + }); + + it("[1,2++,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 10, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [ + {}, + {}, + { collapsible: true, minSizePercentage: 20 }, + ], + pivotIndices: [1, 2], + }) + ).toEqual([25, 75, 0]); + }); + + it("[1,2--,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 1, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([25, 51, 24]); + }); + + it("[1,2--,3]", () => { + expect( + adjustLayoutByDelta({ + delta: 25, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([25, 75, 0]); + }); + + it("[1,2--,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -20, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, { minSizePercentage: 40 }, {}], + pivotIndices: [1, 2], + }) + ).toEqual([15, 40, 45]); + }); + + it("[1,2--,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -10, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [{}, {}, { maxSizePercentage: 30 }], + pivotIndices: [1, 2], + }) + ).toEqual([25, 45, 30]); + }); + + it("[1,2--,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -35, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [ + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + {}, + ], + pivotIndices: [1, 2], + }) + ).toEqual([25, 5, 70]); + }); + + it("[1,2--,3]", () => { + expect( + adjustLayoutByDelta({ + delta: -60, + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + {}, + {}, + ], + pivotIndices: [1, 2], + }) + ).toEqual([5, 0, 95]); + }); + + it("[1++,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 1, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([26, 24, 25, 25]); + }); + + it("[1++,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 25, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([50, 0, 25, 25]); + }); + + it("[1++,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([75, 0, 0, 25]); + }); + + it("[1++,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 75, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([100, 0, 0, 0]); + }); + + it("[1++,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 25, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{ maxSizePercentage: 35 }, {}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([35, 15, 25, 25]); + }); + + it("[1++,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 100, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + {}, + { minSizePercentage: 10 }, + { minSizePercentage: 10 }, + { minSizePercentage: 10 }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([70, 10, 10, 10]); + }); + + it("[1++,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 10, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([45, 5, 25, 25]); + }); + + it("[1++,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 40, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([65, 5, 5, 25]); + }); + + it("[1++,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 100, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([85, 5, 5, 5]); + }); + + it("[1--,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -1, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([24, 26, 25, 25]); + }); + + it("[1--,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -25, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([0, 50, 25, 25]); + }); + + it("[1--,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -10, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{ minSizePercentage: 20 }, {}, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([20, 30, 25, 25]); + }); + + it("[1--,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -25, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, { maxSizePercentage: 35 }, {}, {}], + pivotIndices: [0, 1], + }) + ).toEqual([15, 35, 25, 25]); + }); + + it("[1--,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -10, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + {}, + {}, + {}, + ], + pivotIndices: [0, 1], + }) + ).toEqual([5, 45, 25, 25]); + }); + + it("[1--,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -10, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { maxSizePercentage: 35 }, + {}, + {}, + ], + pivotIndices: [0, 1], + }) + ).toEqual([5, 35, 35, 25]); + }); + + it("[1--,2,3,4]", () => { + // This might be controversial behavior; + // Perhaps the 1st panel should collapse + // rather than being blocked by the max size constraints of the 2nd panel + // since the 3rd panel has room to grow still + // + // An alternate layout result might be: [5, 30, 40, 25] + expect( + adjustLayoutByDelta({ + delta: -10, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { maxSizePercentage: 30 }, + {}, + {}, + ], + pivotIndices: [0, 1], + }) + ).toEqual([20, 30, 25, 25]); + }); + + it("[1--,2,3,4]", () => { + // This might be controversial behavior; + // Perhaps the 1st panel should collapse + // rather than being blocked by the max size constraints of the 2nd panel + // since the 3rd panel has room to grow still + // + // An alternate layout result might be: [5, 30, 35, 30] + expect( + adjustLayoutByDelta({ + delta: -10, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { maxSizePercentage: 30 }, + { maxSizePercentage: 35 }, + {}, + ], + pivotIndices: [0, 1], + }) + ).toEqual([20, 30, 25, 25]); + }); + + it("[1--,2,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -10, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { maxSizePercentage: 35 }, + { maxSizePercentage: 35 }, + { maxSizePercentage: 35 }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([5, 35, 35, 25]); + }); + + it("[1,2++,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 10, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([25, 35, 15, 25]); + }); + + it("[1,2++,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 30, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([25, 55, 0, 20]); + }); + + it("[1,2++,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([25, 75, 0, 0]); + }); + + it("[1,2++,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, { maxSizePercentage: 35 }, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([25, 35, 15, 25]); + }); + + it("[1,2++,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, { minSizePercentage: 20 }, {}], + pivotIndices: [1, 2], + }) + ).toEqual([25, 55, 20, 0]); + }); + + it("[1,2++,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 10, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + {}, + {}, + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 10, + }, + ], + pivotIndices: [1, 2], + }) + ).toEqual([25, 35, 15, 25]); + }); + + it("[1,2++,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 30, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + {}, + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 10, + }, + {}, + ], + pivotIndices: [1, 2], + }) + ).toEqual([25, 55, 5, 15]); + }); + + it("[1,2++,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + {}, + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 10, + }, + { minSizePercentage: 10 }, + ], + pivotIndices: [1, 2], + }) + ).toEqual([25, 60, 5, 10]); + }); + + it("[1,2--,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -25, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([25, 0, 50, 25]); + }); + + it("[1,2--,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([0, 0, 75, 25]); + }); + + it("[1,2--,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, { minSizePercentage: 20 }, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([0, 20, 55, 25]); + }); + + it("[1,2--,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{ minSizePercentage: 20 }, {}, {}, {}], + pivotIndices: [1, 2], + }) + ).toEqual([20, 0, 55, 25]); + }); + + it("[1,2--,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + { minSizePercentage: 20 }, + { minSizePercentage: 20 }, + {}, + {}, + ], + pivotIndices: [1, 2], + }) + ).toEqual([20, 20, 35, 25]); + }); + + it("[1,2--,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -5, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + {}, + {}, + {}, + ], + pivotIndices: [1, 2], + }) + ).toEqual([25, 20, 30, 25]); + }); + + it("[1,2--,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + {}, + {}, + {}, + ], + pivotIndices: [1, 2], + }) + ).toEqual([5, 0, 70, 25]); + }); + + it("[1,2--,3,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + {}, + {}, + ], + pivotIndices: [1, 2], + }) + ).toEqual([0, 5, 70, 25]); + }); + + it("[1,2,3++,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 10, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [2, 3], + }) + ).toEqual([25, 25, 35, 15]); + }); + + it("[1,2,3++,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 30, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [2, 3], + }) + ).toEqual([25, 25, 50, 0]); + }); + + it("[1,2,3++,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 30, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, { maxSizePercentage: 40 }, {}], + pivotIndices: [2, 3], + }) + ).toEqual([25, 25, 40, 10]); + }); + + it("[1,2,3++,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 30, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, { minSizePercentage: 10 }], + pivotIndices: [2, 3], + }) + ).toEqual([25, 25, 40, 10]); + }); + + it("[1,2,3++,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 5, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + {}, + {}, + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + ], + pivotIndices: [2, 3], + }) + ).toEqual([25, 25, 30, 20]); + }); + + it("[1,2,3++,4]", () => { + expect( + adjustLayoutByDelta({ + delta: 50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + {}, + {}, + {}, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + ], + pivotIndices: [2, 3], + }) + ).toEqual([25, 25, 45, 5]); + }); + + it("[1,2,3--,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -10, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [2, 3], + }) + ).toEqual([25, 25, 15, 35]); + }); + + it("[1,2,3--,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -40, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [2, 3], + }) + ).toEqual([25, 10, 0, 65]); + }); + + it("[1,2,3--,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -100, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, {}], + pivotIndices: [2, 3], + }) + ).toEqual([0, 0, 0, 100]); + }); + + it("[1,2,3--,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + { minSizePercentage: 10 }, + { minSizePercentage: 10 }, + { minSizePercentage: 10 }, + {}, + ], + pivotIndices: [2, 3], + }) + ).toEqual([10, 10, 10, 70]); + }); + + it("[1,2,3--,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, {}, {}, { maxSizePercentage: 40 }], + pivotIndices: [2, 3], + }) + ).toEqual([25, 25, 10, 40]); + }); + + it("[1,2,3--,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -50, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [{}, { minSizePercentage: 5 }, {}, {}], + pivotIndices: [2, 3], + }) + ).toEqual([20, 5, 0, 75]); + }); + + it("[1,2,3--,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -100, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + {}, + ], + pivotIndices: [2, 3], + }) + ).toEqual([5, 5, 5, 85]); + }); + + it("[1,2,3--,4]", () => { + expect( + adjustLayoutByDelta({ + delta: -100, + groupSizePixels: NaN, + layout: [25, 25, 25, 25], + panelConstraints: [ + { + minSizePercentage: 20, + }, + { + collapsedSizePercentage: 5, + collapsible: true, + minSizePercentage: 20, + }, + { + minSizePercentage: 20, + }, + {}, + ], + pivotIndices: [2, 3], + }) + ).toEqual([20, 5, 20, 55]); + }); + + // TODO expandToSize after collapsed + + // TODO Invalid pixel constraints (min too large, max too small) should recover by recomputing base? +}); diff --git a/packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.ts b/packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.ts new file mode 100644 index 000000000..0132d4122 --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.ts @@ -0,0 +1,181 @@ +import { computePercentagePanelConstraints } from "./computePercentagePanelConstraints"; +import { fuzzyCompareNumbers } from "./fuzzyCompareNumbers"; +import { resizePanel } from "./resizePanel"; +import { PanelConstraints } from "./types"; + +// All units must be in percentages; pixel values should be pre-converted +export function adjustLayoutByDelta({ + delta, + groupSizePixels, + layout, + panelConstraints, + pivotIndices, +}: { + delta: number; + groupSizePixels: number; + layout: number[]; + panelConstraints: PanelConstraints[]; + // TODO panelExpandToSizes: number[] + pivotIndices: number[]; +}): number[] { + if (delta === 0) { + return layout; + } + + let deltaApplied = 0; + + // A resizing panel affects the panels before or after it. + // + // A negative delta means the panel immediately after the resizer should grow/expand by decreasing its offset. + // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights. + // + // A positive delta means the panel immediately before the resizer should "expand". + // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the resizer. + + // First, check the panel we're pivoting around; + // We should only expand or contract by as much as its constraints allow + { + const pivotIndex = delta < 0 ? pivotIndices[1]! : pivotIndices[0]!; + const initialSize = layout[pivotIndex]!; + + const { collapsible } = panelConstraints[pivotIndex]!; + const { collapsedSizePercentage, minSizePercentage } = + computePercentagePanelConstraints( + panelConstraints, + pivotIndex, + groupSizePixels + ); + const isCollapsed = collapsible && initialSize === collapsedSizePercentage; + + // TODO Could this, combined with the recursion, cause an infinite loop? + const unsafeSize = isCollapsed + ? minSizePercentage + : initialSize + Math.abs(delta); + const safeSize = resizePanel({ + groupSizePixels, + panelConstraints, + panelIndex: pivotIndex, + size: unsafeSize, + }); + + if (initialSize === safeSize) { + // If there's no room for the pivot panel to grow, we should ignore this change + return layout; + } else { + delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize; + } + } + + const initialLayout = [...layout]; + + // Delta added to a panel needs to be subtracted from other panels + // within the constraints that those panels allow + { + const pivotIndex = delta < 0 ? pivotIndices[0]! : pivotIndices[1]!; + let index = pivotIndex; + while (index >= 0 && index < panelConstraints.length) { + const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied); + + const baseSize = initialLayout[index]!; + const unsafeSize = baseSize - deltaRemaining; + + let safeSize = resizePanel({ + groupSizePixels, + panelConstraints, + panelIndex: index, + size: unsafeSize, + }); + + if (baseSize !== safeSize) { + deltaApplied += baseSize - safeSize; + + layout[index] = safeSize; + + if ( + deltaApplied + .toPrecision(3) + .localeCompare(Math.abs(delta).toPrecision(3), undefined, { + numeric: true, + }) >= 0 + ) { + break; + } + } + + if (delta < 0) { + index--; + } else { + index++; + } + } + } + + // If we were unable to resize any of the panels panels, return the previous state. + // This will essentially bailout and ignore e.g. drags past a panel's boundaries + if (fuzzyCompareNumbers(deltaApplied, 0)) { + return initialLayout; + } + + { + const pivotIndex = delta < 0 ? pivotIndices[1]! : pivotIndices[0]!; + + const unsafeSize = initialLayout[pivotIndex]! + deltaApplied; + const safeSize = resizePanel({ + groupSizePixels, + panelConstraints, + panelIndex: pivotIndex, + size: unsafeSize, + }); + + // Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract. + layout[pivotIndex] = safeSize; + + // Edge case where expanding or contracting one panel caused another one to change collapsed state + if (safeSize !== unsafeSize) { + let deltaRemaining = unsafeSize - safeSize; + + const pivotIndex = delta < 0 ? pivotIndices[1]! : pivotIndices[0]!; + let index = pivotIndex; + while (index >= 0 && index < panelConstraints.length) { + const baseSize = layout[index]!; + const unsafeSize = baseSize + deltaRemaining; + const safeSize = resizePanel({ + groupSizePixels, + panelConstraints, + panelIndex: index, + size: unsafeSize, + }); + + if (baseSize !== safeSize) { + deltaRemaining -= safeSize - baseSize; + + layout[index] = safeSize; + } + + if (fuzzyCompareNumbers(deltaRemaining, 0)) { + break; + } + + if (delta > 0) { + index--; + } else { + index++; + } + } + + // If we can't redistribute, this layout is invalid; + // There may be an incremental layout that is valid though + if (!fuzzyCompareNumbers(deltaRemaining, 0)) { + return adjustLayoutByDelta({ + delta: delta < 0 ? delta + 1 : delta - 1, + groupSizePixels, + layout: initialLayout, + panelConstraints, + pivotIndices, + }); + } + } + } + + return layout; +} diff --git a/packages/react-resizable-panels/src/utils/new/compareLayouts.test.ts b/packages/react-resizable-panels/src/utils/new/compareLayouts.test.ts new file mode 100644 index 000000000..69e559b76 --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/compareLayouts.test.ts @@ -0,0 +1,9 @@ +import { compareLayouts } from "./compareLayouts"; + +describe("compareLayouts", () => { + it("should work", () => { + expect(compareLayouts([1, 2], [1])).toBe(false); + expect(compareLayouts([1], [1, 2])).toBe(false); + expect(compareLayouts([1, 2, 3], [1, 2, 3])).toBe(true); + }); +}); diff --git a/packages/react-resizable-panels/src/utils/new/compareLayouts.ts b/packages/react-resizable-panels/src/utils/new/compareLayouts.ts new file mode 100644 index 000000000..04d5f1cbd --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/compareLayouts.ts @@ -0,0 +1,12 @@ +export function compareLayouts(a: number[], b: number[]) { + if (a.length !== b.length) { + return false; + } else { + for (let index = 0; index < a.length; index++) { + if (a[index] != b[index]) { + return false; + } + } + } + return true; +} diff --git a/packages/react-resizable-panels/src/utils/new/computePercentagePanelConstraints.ts b/packages/react-resizable-panels/src/utils/new/computePercentagePanelConstraints.ts new file mode 100644 index 000000000..de0ce12c0 --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/computePercentagePanelConstraints.ts @@ -0,0 +1,109 @@ +import { convertPixelsToPercentage } from "./convertPixelsToPercentage"; +import { PanelConstraints } from "./types"; + +export function computePercentagePanelConstraints( + panelConstraintsArray: PanelConstraints[], + panelIndex: number, + groupSizePixels: number +): { + collapsedSizePercentage: number; + defaultSizePercentage: number | undefined; + maxSizePercentage: number; + minSizePercentage: number; +} { + // All panel constraints, excluding the current one + let totalMinConstraints = 0; + let totalMaxConstraints = 0; + + for (let index = 0; index < panelConstraintsArray.length; index++) { + if (index !== panelIndex) { + const { collapsible } = panelConstraintsArray[index]!; + const { collapsedSizePercentage, maxSizePercentage, minSizePercentage } = + convertPixelConstraintsToPercentages( + panelConstraintsArray[index]!, + groupSizePixels + ); + + totalMaxConstraints += maxSizePercentage; + totalMinConstraints += collapsible + ? collapsedSizePercentage + : minSizePercentage; + } + } + + const { + collapsedSizePercentage, + defaultSizePercentage, + maxSizePercentage, + minSizePercentage, + } = convertPixelConstraintsToPercentages( + panelConstraintsArray[panelIndex]!, + groupSizePixels + ); + + return { + collapsedSizePercentage, + defaultSizePercentage, + maxSizePercentage: + panelConstraintsArray.length > 1 + ? Math.min(maxSizePercentage, 100 - totalMinConstraints) + : maxSizePercentage, + minSizePercentage: + panelConstraintsArray.length > 1 + ? Math.max(minSizePercentage, 100 - totalMaxConstraints) + : minSizePercentage, + }; +} + +export function convertPixelConstraintsToPercentages( + panelConstraints: PanelConstraints, + groupSizePixels: number +): { + collapsedSizePercentage: number; + defaultSizePercentage: number | undefined; + maxSizePercentage: number; + minSizePercentage: number; +} { + let { + collapsedSizePercentage = 0, + collapsedSizePixels, + defaultSizePercentage, + defaultSizePixels, + maxSizePercentage = 100, + maxSizePixels, + minSizePercentage = 0, + minSizePixels, + } = panelConstraints; + + if (collapsedSizePixels != null) { + collapsedSizePercentage = convertPixelsToPercentage( + collapsedSizePixels, + groupSizePixels + ); + } + if (defaultSizePixels != null) { + defaultSizePercentage = convertPixelsToPercentage( + defaultSizePixels, + groupSizePixels + ); + } + if (minSizePixels != null) { + minSizePercentage = convertPixelsToPercentage( + minSizePixels, + groupSizePixels + ); + } + if (maxSizePixels != null) { + maxSizePercentage = convertPixelsToPercentage( + maxSizePixels, + groupSizePixels + ); + } + + return { + collapsedSizePercentage, + defaultSizePercentage, + maxSizePercentage, + minSizePercentage, + }; +} diff --git a/packages/react-resizable-panels/src/utils/new/convertPercentageToPixels.test.ts b/packages/react-resizable-panels/src/utils/new/convertPercentageToPixels.test.ts new file mode 100644 index 000000000..fa294087c --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/convertPercentageToPixels.test.ts @@ -0,0 +1,9 @@ +import { convertPercentageToPixels } from "./convertPercentageToPixels"; + +describe("convertPercentageToPixels", () => { + it("should convert percentages to pixels", () => { + expect(convertPercentageToPixels(0, 100_000)).toBe(0); + expect(convertPercentageToPixels(50, 100_000)).toBe(50_000); + expect(convertPercentageToPixels(100, 100_000)).toBe(100_000); + }); +}); diff --git a/packages/react-resizable-panels/src/utils/new/convertPercentageToPixels.ts b/packages/react-resizable-panels/src/utils/new/convertPercentageToPixels.ts new file mode 100644 index 000000000..dc351451f --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/convertPercentageToPixels.ts @@ -0,0 +1,6 @@ +export function convertPercentageToPixels( + percentage: number, + groupSizePixels: number +): number { + return (percentage / 100) * groupSizePixels; +} diff --git a/packages/react-resizable-panels/src/utils/new/convertPixelsToPercentage.test.ts b/packages/react-resizable-panels/src/utils/new/convertPixelsToPercentage.test.ts new file mode 100644 index 000000000..e8e15818a --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/convertPixelsToPercentage.test.ts @@ -0,0 +1,9 @@ +import { convertPixelsToPercentage } from "./convertPixelsToPercentage"; + +describe("convertPixelsToPercentage", () => { + it("should convert pixels to percentages", () => { + expect(convertPixelsToPercentage(0, 100_000)).toBe(0); + expect(convertPixelsToPercentage(50_000, 100_000)).toBe(50); + expect(convertPixelsToPercentage(100_000, 100_000)).toBe(100); + }); +}); diff --git a/packages/react-resizable-panels/src/utils/new/convertPixelsToPercentage.ts b/packages/react-resizable-panels/src/utils/new/convertPixelsToPercentage.ts new file mode 100644 index 000000000..e83bffb71 --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/convertPixelsToPercentage.ts @@ -0,0 +1,6 @@ +export function convertPixelsToPercentage( + pixels: number, + groupSizePixels: number +): number { + return (pixels / groupSizePixels) * 100; +} diff --git a/packages/react-resizable-panels/src/utils/new/fuzzyCompareNumbers.ts b/packages/react-resizable-panels/src/utils/new/fuzzyCompareNumbers.ts new file mode 100644 index 000000000..ae65efd26 --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/fuzzyCompareNumbers.ts @@ -0,0 +1,7 @@ +export function fuzzyCompareNumbers( + actual: number, + expected: number, + fractionDigits: number = 3 +) { + return actual.toFixed(fractionDigits) === expected.toFixed(fractionDigits); +} diff --git a/packages/react-resizable-panels/src/utils/new/resizePanel.ts b/packages/react-resizable-panels/src/utils/new/resizePanel.ts new file mode 100644 index 000000000..a2b523deb --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/resizePanel.ts @@ -0,0 +1,40 @@ +import { computePercentagePanelConstraints } from "./computePercentagePanelConstraints"; +import { PanelConstraints } from "./types"; + +// Panel size must be in percentages; pixel values should be pre-converted +export function resizePanel({ + groupSizePixels, + panelConstraints, + panelIndex, + size, +}: { + groupSizePixels: number; + panelConstraints: PanelConstraints[]; + panelIndex: number; + size: number; +}) { + let { collapsible } = panelConstraints[panelIndex]!; + + const { collapsedSizePercentage, maxSizePercentage, minSizePercentage } = + computePercentagePanelConstraints( + panelConstraints, + panelIndex, + groupSizePixels + ); + + if (minSizePercentage != null) { + if (size < minSizePercentage) { + if (collapsible) { + size = collapsedSizePercentage; + } else { + size = minSizePercentage; + } + } + } + + if (maxSizePercentage != null) { + size = Math.min(maxSizePercentage, size); + } + + return size; +} diff --git a/packages/react-resizable-panels/src/utils/new/test-utils.ts b/packages/react-resizable-panels/src/utils/new/test-utils.ts new file mode 100644 index 000000000..fc15d45a2 --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/test-utils.ts @@ -0,0 +1,53 @@ +const util = require("util"); + +export function verifyExpectedWarnings( + callback: Function, + ...expectedMessages: string[] +) { + const consoleSpy = (format: any, ...args: any[]) => { + const message = util.format(format, ...args); + + for (let index = 0; index < expectedMessages.length; index++) { + const expectedMessage = expectedMessages[index]; + if (message.includes(expectedMessage)) { + expectedMessages.splice(index, 1); + return; + } + } + + if (expectedMessages.length === 0) { + throw new Error(`Unexpected message recorded:\n\n${message}`); + } + }; + + const originalError = console.error; + const originalWarn = console.warn; + + console.error = consoleSpy; + console.warn = consoleSpy; + + let caughtError; + let didCatch = false; + try { + callback(); + } catch (error) { + caughtError = error; + didCatch = true; + } finally { + console.error = originalError; + console.warn = originalWarn; + + if (didCatch) { + throw caughtError; + } + + // Any remaining messages indicate a failed expectations. + if (expectedMessages.length > 0) { + throw Error( + `Expected message(s) not recorded:\n\n${expectedMessages.join("\n")}` + ); + } + + return { pass: true }; + } +} diff --git a/packages/react-resizable-panels/src/utils/new/types.ts b/packages/react-resizable-panels/src/utils/new/types.ts new file mode 100644 index 000000000..9ba6c3658 --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/types.ts @@ -0,0 +1,11 @@ +export type PanelConstraints = { + collapsedSizePercentage?: number | undefined; + collapsedSizePixels?: number | undefined; + collapsible?: boolean | undefined; + defaultSizePercentage?: number | undefined; + defaultSizePixels?: number | undefined; + maxSizePercentage?: number | undefined; + maxSizePixels?: number | undefined; + minSizePercentage?: number | undefined; + minSizePixels?: number | undefined; +}; diff --git a/packages/react-resizable-panels/src/utils/new/validatePanelConstraints.test.ts b/packages/react-resizable-panels/src/utils/new/validatePanelConstraints.test.ts new file mode 100644 index 000000000..f26d680db --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/validatePanelConstraints.test.ts @@ -0,0 +1,115 @@ +import { verifyExpectedWarnings } from "./test-utils"; +import { validatePanelConstraints } from "./validatePanelConstraints"; + +describe("validatePanelConstraints", () => { + it("should not warn if there are no validation errors", () => { + verifyExpectedWarnings(() => { + validatePanelConstraints({ + panelConstraints: [{}], + panelIndex: 0, + panelId: "test", + }); + }); + }); + + it("should warn about conflicting percentages and pixels", () => { + verifyExpectedWarnings(() => { + validatePanelConstraints({ + panelConstraints: [ + { + collapsedSizePercentage: 5, + collapsedSizePixels: 10, + }, + ], + panelIndex: 0, + panelId: "test", + }); + }, "should not specify both percentage and pixel units for: collapsed size"); + + verifyExpectedWarnings(() => { + validatePanelConstraints({ + panelConstraints: [ + { + maxSizePercentage: 5, + maxSizePixels: 10, + minSizePercentage: 5, + minSizePixels: 10, + }, + ], + panelIndex: 0, + panelId: "test", + }); + }, "should not specify both percentage and pixel units for: max size, min size"); + + verifyExpectedWarnings(() => { + validatePanelConstraints({ + panelConstraints: [ + { + defaultSizePercentage: 5, + defaultSizePixels: 10, + }, + ], + panelIndex: 0, + panelId: "test", + }); + }, "should not specify both percentage and pixel units for: default size"); + }); + + it("should warn about conflicting min/max sizes", () => { + verifyExpectedWarnings(() => { + validatePanelConstraints({ + panelConstraints: [ + { + maxSizePercentage: 5, + minSizePercentage: 10, + }, + ], + panelIndex: 0, + panelId: "test", + }); + }, "min size (10%) should not be greater than max size (5%)"); + }); + + it("should warn about conflicting collapsed and min sizes", () => { + verifyExpectedWarnings(() => { + validatePanelConstraints({ + panelConstraints: [ + { + collapsedSizePercentage: 15, + minSizePercentage: 10, + }, + ], + panelIndex: 0, + panelId: "test", + }); + }, "collapsed size should not be greater than min size"); + }); + + it("should warn about conflicting default and min/max sizes", () => { + verifyExpectedWarnings(() => { + validatePanelConstraints({ + panelConstraints: [ + { + defaultSizePercentage: 5, + minSizePercentage: 10, + }, + ], + panelIndex: 0, + panelId: "test", + }); + }, "default size should not be less than min size"); + + verifyExpectedWarnings(() => { + validatePanelConstraints({ + panelConstraints: [ + { + defaultSizePercentage: 15, + maxSizePercentage: 10, + }, + ], + panelIndex: 0, + panelId: "test", + }); + }, "default size should not be greater than max size"); + }); +}); diff --git a/packages/react-resizable-panels/src/utils/new/validatePanelConstraints.ts b/packages/react-resizable-panels/src/utils/new/validatePanelConstraints.ts new file mode 100644 index 000000000..cb7aeb337 --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/validatePanelConstraints.ts @@ -0,0 +1,88 @@ +import { isDevelopment } from "#is-development"; +import { computePercentagePanelConstraints } from "./computePercentagePanelConstraints"; +import { PanelConstraints } from "./types"; + +export function validatePanelConstraints({ + panelConstraints, + panelId, + panelIndex, +}: { + panelConstraints: PanelConstraints[]; + panelId: string | undefined; + panelIndex: number; +}) { + if (isDevelopment) { + const warnings = []; + + { + const { + collapsedSizePercentage, + collapsedSizePixels, + defaultSizePercentage, + defaultSizePixels, + maxSizePercentage, + maxSizePixels, + minSizePercentage, + minSizePixels, + } = panelConstraints[panelIndex]!; + + const conflictingUnits: string[] = []; + + if (collapsedSizePercentage != null && collapsedSizePixels != null) { + conflictingUnits.push("collapsed size"); + } + if (defaultSizePercentage != null && defaultSizePixels != null) { + conflictingUnits.push("default size"); + } + if (maxSizePercentage != null && maxSizePixels != null) { + conflictingUnits.push("max size"); + } + if (minSizePercentage != null && minSizePixels != null) { + conflictingUnits.push("min size"); + } + + if (conflictingUnits.length > 0) { + warnings.push( + `should not specify both percentage and pixel units for: ${conflictingUnits.join( + ", " + )}` + ); + } + } + + { + const { + collapsedSizePercentage, + defaultSizePercentage, + maxSizePercentage, + minSizePercentage, + } = computePercentagePanelConstraints(panelConstraints, panelIndex, 100); + + if (minSizePercentage > maxSizePercentage) { + warnings.push( + `min size (${minSizePercentage}%) should not be greater than max size (${maxSizePercentage}%)` + ); + } + + if (defaultSizePercentage != null) { + if (defaultSizePercentage < minSizePercentage) { + warnings.push("default size should not be less than min size"); + } + if (defaultSizePercentage > maxSizePercentage) { + warnings.push("default size should not be greater than max size"); + } + } + + if (collapsedSizePercentage > minSizePercentage) { + warnings.push("collapsed size should not be greater than min size"); + } + } + + if (warnings.length > 0) { + const name = panelId != null ? `Panel "${panelId}"` : "Panel"; + console.error( + `${name} has an invalid configuration:\n\n${warnings.join("\n")}` + ); + } + } +} diff --git a/packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.test.ts b/packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.test.ts new file mode 100644 index 000000000..c1695b580 --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.test.ts @@ -0,0 +1,228 @@ +import { validatePanelGroupLayout } from "./validatePanelGroupLayout"; + +describe("validatePanelGroupLayout", () => { + it("should accept requested layout if there are no constraints provided", () => { + expect( + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [10, 60, 30], + panelConstraints: [{}, {}, {}], + }) + ).toEqual([10, 60, 30]); + }); + + it("should reject layouts that do not total 100%", () => { + expect(() => + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [10, 20, 30], + panelConstraints: [{}, {}, {}], + }) + ).toThrow("Invalid layout total size"); + + expect(() => + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [50, 100, 150], + panelConstraints: [{}, {}, {}], + }) + ).toThrow("Invalid layout total size"); + }); + + it("should reject layouts that do not match the number of panels", () => { + expect(() => + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [10, 20, 30], + panelConstraints: [{}, {}], + }) + ).toThrow("Invalid 2 panel layout"); + + expect(() => + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [50, 50], + panelConstraints: [{}, {}, {}], + }) + ).toThrow("Invalid 3 panel layout"); + }); + + describe("minimum size constraints", () => { + it("should adjust the layout to account for minimum percentage sizes", () => { + expect( + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [25, 75], + panelConstraints: [ + { + minSizePercentage: 35, + }, + {}, + ], + }) + ).toEqual([35, 65]); + }); + + it("should adjust the layout to account for minimum pixel sizes", () => { + expect( + validatePanelGroupLayout({ + groupSizePixels: 400, + layout: [20, 80], + panelConstraints: [ + { + minSizePixels: 100, + }, + {}, + ], + }) + ).toEqual([25, 75]); + }); + + it("should account for multiple panels with minimum size constraints", () => { + expect( + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [20, 60, 20], + panelConstraints: [ + { + minSizePercentage: 25, + }, + {}, + { + minSizePercentage: 25, + }, + ], + }) + ).toEqual([25, 50, 25]); + }); + }); + + describe("maximum size constraints", () => { + it("should adjust the layout to account for maximum percentage sizes", () => { + expect( + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [25, 75], + panelConstraints: [{}, { maxSizePercentage: 65 }], + }) + ).toEqual([35, 65]); + }); + + it("should adjust the layout to account for maximum pixel sizes", () => { + expect( + validatePanelGroupLayout({ + groupSizePixels: 400, + layout: [20, 80], + panelConstraints: [ + {}, + { + maxSizePixels: 100, + }, + ], + }) + ).toEqual([75, 25]); + }); + + it("should account for multiple panels with maximum size constraints", () => { + expect( + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [20, 60, 20], + panelConstraints: [ + { + maxSizePercentage: 15, + }, + { maxSizePercentage: 50 }, + {}, + ], + }) + ).toEqual([15, 50, 35]); + }); + }); + + describe("collapsible panels", () => { + it("should not collapse a panel that's at or above the minimum size", () => { + expect( + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [25, 75], + panelConstraints: [{ collapsible: true, minSizePercentage: 25 }, {}], + }) + ).toEqual([25, 75]); + }); + + it("should collapse a panel that's below the minimum percentage size", () => { + expect( + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [20, 80], + panelConstraints: [ + { + collapsible: true, + collapsedSizePercentage: 10, + minSizePercentage: 25, + }, + {}, + ], + }) + ).toEqual([10, 90]); + }); + + it("should collapse a panel that's below the minimum pixel size", () => { + expect( + validatePanelGroupLayout({ + groupSizePixels: 400, + layout: [20, 80], + panelConstraints: [ + { + collapsible: true, + collapsedSizePixels: 40, + minSizePixels: 100, + }, + {}, + ], + }) + ).toEqual([10, 90]); + }); + }); + + describe("combination of minimum and maximum size constraints", () => { + it("three panel min/max configuration", () => { + expect( + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [25, 50, 25], + panelConstraints: [ + { minSizePercentage: 10, maxSizePercentage: 25 }, + { maxSizePercentage: 75 }, + { minSizePercentage: 10, maxSizePercentage: 50 }, + ], + }) + ).toEqual([25, 50, 25]); + + expect( + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [5, 80, 15], + panelConstraints: [ + { minSizePercentage: 10, maxSizePercentage: 25 }, + { maxSizePercentage: 75 }, + { minSizePercentage: 10, maxSizePercentage: 50 }, + ], + }) + ).toEqual([10, 75, 15]); + + expect( + validatePanelGroupLayout({ + groupSizePixels: NaN, + layout: [30, 10, 60], + panelConstraints: [ + { minSizePercentage: 10, maxSizePercentage: 25 }, + { maxSizePercentage: 75 }, + { minSizePercentage: 10, maxSizePercentage: 50 }, + ], + }) + ).toEqual([25, 25, 50]); + }); + }); +}); diff --git a/packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.ts b/packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.ts new file mode 100644 index 000000000..94c9bd8e4 --- /dev/null +++ b/packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.ts @@ -0,0 +1,81 @@ +import { fuzzyCompareNumbers } from "./fuzzyCompareNumbers"; +import { resizePanel } from "./resizePanel"; +import { PanelConstraints } from "./types"; + +// All units must be in percentages; pixel values should be pre-converted +export function validatePanelGroupLayout({ + groupSizePixels, + layout, + panelConstraints, +}: { + groupSizePixels: number; + layout: number[]; + panelConstraints: PanelConstraints[]; +}): number[] { + // Validate layout expectations + if (layout.length !== panelConstraints.length) { + throw Error( + `Invalid ${panelConstraints.length} panel layout: ${layout + .map((size) => `${size}%`) + .join(", ")}` + ); + } else if ( + !fuzzyCompareNumbers( + layout.reduce((accumulated, current) => accumulated + current, 0), + 100 + ) + ) { + throw Error( + `Invalid layout total size: ${layout + .map((size) => `${size}%`) + .join(", ")}` + ); + } + + let remainingSize = 0; + + // First pass: Validate the proposed layout given each panel's constraints + for (let index = 0; index < panelConstraints.length; index++) { + const unsafeSize = layout[index]!; + + const safeSize = resizePanel({ + groupSizePixels, + panelConstraints, + panelIndex: index, + size: unsafeSize, + }); + + if (unsafeSize != safeSize) { + remainingSize += unsafeSize - safeSize; + + layout[index] = safeSize; + } + } + + // If there is additional, left over space, assign it to any panel(s) that permits it + // (It's not worth taking multiple additional passes to evenly distribute) + if (!fuzzyCompareNumbers(remainingSize, 0)) { + for (let index = 0; index < panelConstraints.length; index++) { + const prevSize = layout[index]!; + const unsafeSize = prevSize + remainingSize; + const safeSize = resizePanel({ + groupSizePixels, + panelConstraints, + panelIndex: index, + size: unsafeSize, + }); + + if (prevSize !== safeSize) { + remainingSize -= safeSize - prevSize; + layout[index] = safeSize; + + // Once we've used up the remainder, bail + if (fuzzyCompareNumbers(remainingSize, 0)) { + break; + } + } + } + } + + return layout; +} From a2929c1036ae6d87b346791d0ee6c9ab7ed47bcd Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 14 Aug 2023 18:36:08 -0400 Subject: [PATCH 04/35] Update PNPM installation for GH Actions --- .github/workflows/e2e-ci.yml | 2 +- .github/workflows/eslint.yml | 2 +- .github/workflows/jest.yml | 2 +- .github/workflows/prettier.yml | 2 +- .github/workflows/typescript.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e-ci.yml b/.github/workflows/e2e-ci.yml index 25564fc52..b8e59a724 100644 --- a/.github/workflows/e2e-ci.yml +++ b/.github/workflows/e2e-ci.yml @@ -10,7 +10,7 @@ jobs: with: version: 7 - name: Install dependencies - run: pnpm install -r + run: pnpm install --no-frozen-lockfile --recursive - name: Install Playwright dependencies run: npx playwright install - name: Build NPM package diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 1a7315fe7..09f9696f1 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -10,6 +10,6 @@ jobs: with: version: 7 - name: Install dependencies - run: pnpm install -r + run: pnpm install --no-frozen-lockfile --recursive - name: Run ESLint run: pnpm lint diff --git a/.github/workflows/jest.yml b/.github/workflows/jest.yml index ffe533a97..2bc5c2064 100644 --- a/.github/workflows/jest.yml +++ b/.github/workflows/jest.yml @@ -10,7 +10,7 @@ jobs: with: version: 7 - name: Install dependencies - run: pnpm install -r + run: pnpm install --no-frozen-lockfile --recursive - name: Build NPM packages run: pnpm run prerelease - name: Run tests diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 60cf63ef7..fa5f1e1b6 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -10,6 +10,6 @@ jobs: with: version: 7 - name: Install dependencies - run: pnpm install -r + run: pnpm install --no-frozen-lockfile --recursive - name: Run Prettier run: pnpm run prettier:ci diff --git a/.github/workflows/typescript.yml b/.github/workflows/typescript.yml index d5e962751..8b924da3c 100644 --- a/.github/workflows/typescript.yml +++ b/.github/workflows/typescript.yml @@ -10,7 +10,7 @@ jobs: with: version: 7 - name: Install dependencies - run: pnpm install -r + run: pnpm install --no-frozen-lockfile --recursive - name: Build NPM package run: pnpm prerelease - name: Run TypeScript From 4904c7a06c1e6e1fbfd8746f07ae6d32c3f1f23a Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 27 Aug 2023 17:05:04 -0400 Subject: [PATCH 05/35] Partially migrated Panel/PanelGroup/PanelResizeHandle to new internal API --- .../src/components/ResizeHandle.tsx | 30 +- .../src/routes/examples/Collapsible.tsx | 30 +- .../src/routes/examples/Conditional.tsx | 28 +- .../src/routes/examples/Horizontal.tsx | 27 +- .../src/routes/examples/Nested.tsx | 33 +- .../src/routes/examples/Overflow.tsx | 19 +- .../src/routes/examples/Persistence.tsx | 13 +- .../src/routes/examples/Vertical.tsx | 23 +- .../react-resizable-panels/.eslintrc.json | 2 +- packages/react-resizable-panels/src/index.ts | 10 + .../react-resizable-panels/src/new/Panel.ts | 242 +++++++ .../src/new/PanelGroup.ts | 591 ++++++++++++++++++ .../src/new/PanelGroupContext.ts | 33 + .../src/new/PanelResizeHandle.ts | 193 ++++++ .../react-resizable-panels/src/new/types.ts | 6 + .../utils}/adjustLayoutByDelta.test.ts | 290 ++++++--- .../new => new/utils}/adjustLayoutByDelta.ts | 115 ++-- .../src/new/utils/calculateDefaultLayout.ts | 57 ++ .../src/new/utils/calculateDeltaPercentage.ts | 56 ++ .../utils/calculateDragOffsetPercentage.ts | 29 + .../new => new/utils}/compareLayouts.test.ts | 0 .../new => new/utils}/compareLayouts.ts | 0 .../src/new/utils/computePanelFlexBoxStyle.ts | 44 ++ .../computePercentagePanelConstraints.ts | 57 +- .../utils}/convertPercentageToPixels.test.ts | 0 .../utils}/convertPercentageToPixels.ts | 0 .../convertPixelConstraintsToPercentages.ts | 55 ++ .../utils}/convertPixelsToPercentage.test.ts | 0 .../utils}/convertPixelsToPercentage.ts | 0 .../src/new/utils/determinePivotIndices.ts | 12 + .../src/new/utils/events.ts | 13 + .../src/new/utils/fuzzyCompareNumbers.test.ts | 16 + .../src/new/utils/fuzzyCompareNumbers.ts | 17 + .../src/new/utils/fuzzyNumbersEqual.ts | 9 + .../new/utils/getResizeEventCursorPosition.ts | 19 + .../src/new/utils/initializeDefaultStorage.ts | 26 + .../{utils/new => new/utils}/resizePanel.ts | 6 +- .../src/new/utils/serialization.ts | 70 +++ .../{utils/new => new/utils}/test-utils.ts | 0 .../utils}/validatePanelConstraints.test.ts | 0 .../utils}/validatePanelConstraints.ts | 2 +- .../utils}/validatePanelGroupLayout.test.ts | 0 .../utils}/validatePanelGroupLayout.ts | 32 +- .../src/utils/new/fuzzyCompareNumbers.ts | 7 - .../src/utils/new/types.ts | 11 - .../src/vendor/react.ts | 2 + tsconfig.json | 1 + 47 files changed, 1949 insertions(+), 277 deletions(-) create mode 100644 packages/react-resizable-panels/src/new/Panel.ts create mode 100644 packages/react-resizable-panels/src/new/PanelGroup.ts create mode 100644 packages/react-resizable-panels/src/new/PanelGroupContext.ts create mode 100644 packages/react-resizable-panels/src/new/PanelResizeHandle.ts create mode 100644 packages/react-resizable-panels/src/new/types.ts rename packages/react-resizable-panels/src/{utils/new => new/utils}/adjustLayoutByDelta.test.ts (85%) rename packages/react-resizable-panels/src/{utils/new => new/utils}/adjustLayoutByDelta.ts (59%) create mode 100644 packages/react-resizable-panels/src/new/utils/calculateDefaultLayout.ts create mode 100644 packages/react-resizable-panels/src/new/utils/calculateDeltaPercentage.ts create mode 100644 packages/react-resizable-panels/src/new/utils/calculateDragOffsetPercentage.ts rename packages/react-resizable-panels/src/{utils/new => new/utils}/compareLayouts.test.ts (100%) rename packages/react-resizable-panels/src/{utils/new => new/utils}/compareLayouts.ts (100%) create mode 100644 packages/react-resizable-panels/src/new/utils/computePanelFlexBoxStyle.ts rename packages/react-resizable-panels/src/{utils/new => new/utils}/computePercentagePanelConstraints.ts (54%) rename packages/react-resizable-panels/src/{utils/new => new/utils}/convertPercentageToPixels.test.ts (100%) rename packages/react-resizable-panels/src/{utils/new => new/utils}/convertPercentageToPixels.ts (100%) create mode 100644 packages/react-resizable-panels/src/new/utils/convertPixelConstraintsToPercentages.ts rename packages/react-resizable-panels/src/{utils/new => new/utils}/convertPixelsToPercentage.test.ts (100%) rename packages/react-resizable-panels/src/{utils/new => new/utils}/convertPixelsToPercentage.ts (100%) create mode 100644 packages/react-resizable-panels/src/new/utils/determinePivotIndices.ts create mode 100644 packages/react-resizable-panels/src/new/utils/events.ts create mode 100644 packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.test.ts create mode 100644 packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.ts create mode 100644 packages/react-resizable-panels/src/new/utils/fuzzyNumbersEqual.ts create mode 100644 packages/react-resizable-panels/src/new/utils/getResizeEventCursorPosition.ts create mode 100644 packages/react-resizable-panels/src/new/utils/initializeDefaultStorage.ts rename packages/react-resizable-panels/src/{utils/new => new/utils}/resizePanel.ts (80%) create mode 100644 packages/react-resizable-panels/src/new/utils/serialization.ts rename packages/react-resizable-panels/src/{utils/new => new/utils}/test-utils.ts (100%) rename packages/react-resizable-panels/src/{utils/new => new/utils}/validatePanelConstraints.test.ts (100%) rename packages/react-resizable-panels/src/{utils/new => new/utils}/validatePanelConstraints.ts (98%) rename packages/react-resizable-panels/src/{utils/new => new/utils}/validatePanelGroupLayout.test.ts (100%) rename packages/react-resizable-panels/src/{utils/new => new/utils}/validatePanelGroupLayout.ts (68%) delete mode 100644 packages/react-resizable-panels/src/utils/new/fuzzyCompareNumbers.ts delete mode 100644 packages/react-resizable-panels/src/utils/new/types.ts diff --git a/packages/react-resizable-panels-website/src/components/ResizeHandle.tsx b/packages/react-resizable-panels-website/src/components/ResizeHandle.tsx index be4786299..43a1c93cc 100644 --- a/packages/react-resizable-panels-website/src/components/ResizeHandle.tsx +++ b/packages/react-resizable-panels-website/src/components/ResizeHandle.tsx @@ -1,4 +1,7 @@ -import { PanelResizeHandle } from "react-resizable-panels"; +import { + PanelResizeHandle, + new_PanelResizeHandle as NewPanelResizeHandle, +} from "react-resizable-panels"; import Icon from "./Icon"; import styles from "./ResizeHandle.module.css"; @@ -27,3 +30,28 @@ export default function ResizeHandle({ ); } + +export function new_ResizeHandle({ + className = "", + collapsed = false, + id, +}: { + className?: string; + collapsed?: boolean; + id?: string; +}) { + return ( + +
+ + +
+
+ ); +} diff --git a/packages/react-resizable-panels-website/src/routes/examples/Collapsible.tsx b/packages/react-resizable-panels-website/src/routes/examples/Collapsible.tsx index 02968bc37..efff3ebdd 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/Collapsible.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/Collapsible.tsx @@ -1,5 +1,10 @@ import { useReducer } from "react"; -import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; + +import { + new_Panel as Panel, + new_PanelGroup as PanelGroup, + new_PanelResizeHandle as PanelResizeHandle, +} from "react-resizable-panels"; import { TUTORIAL_CODE_CSS, @@ -56,25 +61,30 @@ function Content() { dispatch({ type: "open", file }); }; - const toggleCollapsed = (collapsed: boolean) => { - dispatch({ type: "toggleCollapsed", collapsed }); + const onCollapse = () => { + dispatch({ type: "toggleCollapsed", collapsed: false }); + }; + + const onExpand = () => { + dispatch({ type: "toggleCollapsed", collapsed: true }); }; return (
- +
@@ -103,7 +113,7 @@ function Content() { : styles.ResizeHandle } /> - +
{Array.from(openFiles).map((file) => (
{showLeftPanel && ( <> - +
left
)} - +
middle
{showRightPanel && ( <> - +
right
diff --git a/packages/react-resizable-panels-website/src/routes/examples/Horizontal.tsx b/packages/react-resizable-panels-website/src/routes/examples/Horizontal.tsx index 69070500b..240480d86 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/Horizontal.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/Horizontal.tsx @@ -1,6 +1,9 @@ -import { Panel, PanelGroup } from "react-resizable-panels"; +import { + new_Panel as Panel, + new_PanelGroup as PanelGroup, +} from "react-resizable-panels"; -import ResizeHandle from "../../components/ResizeHandle"; +import { new_ResizeHandle as ResizeHandle } from "../../components/ResizeHandle"; import Example from "./Example"; import styles from "./shared.module.css"; @@ -33,15 +36,23 @@ function Content() { return (
- +
left
- +
middle
- +
right
@@ -51,15 +62,15 @@ function Content() { const CODE = ` - + left - + middle - + right diff --git a/packages/react-resizable-panels-website/src/routes/examples/Nested.tsx b/packages/react-resizable-panels-website/src/routes/examples/Nested.tsx index e7adfb706..bed4eb9b1 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/Nested.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/Nested.tsx @@ -1,6 +1,9 @@ -import { Panel, PanelGroup } from "react-resizable-panels"; +import { + new_Panel as Panel, + new_PanelGroup as PanelGroup, +} from "react-resizable-panels"; -import ResizeHandle from "../../components/ResizeHandle"; +import { new_ResizeHandle as ResizeHandle } from "../../components/ResizeHandle"; import Example from "./Example"; import styles from "./shared.module.css"; @@ -20,23 +23,31 @@ function Content() { return (
- +
left
- + - +
top
- + - +
left
- +
right
@@ -44,7 +55,11 @@ function Content() {
- +
right
diff --git a/packages/react-resizable-panels-website/src/routes/examples/Overflow.tsx b/packages/react-resizable-panels-website/src/routes/examples/Overflow.tsx index a2ba18a40..2608acca7 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/Overflow.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/Overflow.tsx @@ -1,8 +1,11 @@ import { useLayoutEffect, useRef, useState } from "react"; -import { Panel, PanelGroup } from "react-resizable-panels"; +import { + new_Panel as Panel, + new_PanelGroup as PanelGroup, +} from "react-resizable-panels"; import Code from "../../components/Code"; -import ResizeHandle from "../../components/ResizeHandle"; +import { new_ResizeHandle as ResizeHandle } from "../../components/ResizeHandle"; import Example from "./Example"; import styles from "./shared.module.css"; @@ -54,7 +57,11 @@ function Content() { className={styles.PanelGroup} direction={useVerticalLayout ? "vertical" : "horizontal"} > - +
- +
- +
left
- +
middle
- +
right
diff --git a/packages/react-resizable-panels-website/src/routes/examples/Vertical.tsx b/packages/react-resizable-panels-website/src/routes/examples/Vertical.tsx index 71abcd520..d785d5498 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/Vertical.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/Vertical.tsx @@ -1,6 +1,9 @@ -import { Panel, PanelGroup } from "react-resizable-panels"; +import { + new_Panel as Panel, + new_PanelGroup as PanelGroup, +} from "react-resizable-panels"; -import ResizeHandle from "../../components/ResizeHandle"; +import { new_ResizeHandle as ResizeHandle } from "../../components/ResizeHandle"; import Example from "./Example"; import styles from "./shared.module.css"; @@ -35,14 +38,18 @@ function Content() {
top
- +
bottom
@@ -52,11 +59,11 @@ function Content() { const CODE = ` - + top - + bottom diff --git a/packages/react-resizable-panels/.eslintrc.json b/packages/react-resizable-panels/.eslintrc.json index 160e181d7..a63abb222 100644 --- a/packages/react-resizable-panels/.eslintrc.json +++ b/packages/react-resizable-panels/.eslintrc.json @@ -1,7 +1,7 @@ { "ignorePatterns": [".parcel-cache", "dist", "node_modules"], "parser": "@typescript-eslint/parser", - "parserOptions": { "project": ["../../tsconfig.json"] }, + "parserOptions": { "project": ["tsconfig.json"] }, "plugins": ["@typescript-eslint", "no-restricted-imports", "react-hooks"], "root": true, "rules": { diff --git a/packages/react-resizable-panels/src/index.ts b/packages/react-resizable-panels/src/index.ts index 331599162..74385ddf5 100644 --- a/packages/react-resizable-panels/src/index.ts +++ b/packages/react-resizable-panels/src/index.ts @@ -2,6 +2,11 @@ import { Panel } from "./Panel"; import { PanelGroup } from "./PanelGroup"; import { PanelResizeHandle } from "./PanelResizeHandle"; +// TEMP +import { Panel as new_Panel } from "./new/Panel"; +import { PanelGroup as new_PanelGroup } from "./new/PanelGroup"; +import { PanelResizeHandle as new_PanelResizeHandle } from "./new/PanelResizeHandle"; + import type { ImperativePanelHandle, PanelProps } from "./Panel"; import type { ImperativePanelGroupHandle, PanelGroupProps } from "./PanelGroup"; import type { PanelResizeHandleProps } from "./PanelResizeHandle"; @@ -36,4 +41,9 @@ export { // Utility methods getAvailableGroupSizePixels, + + // TEMP + new_Panel, + new_PanelGroup, + new_PanelResizeHandle, }; diff --git a/packages/react-resizable-panels/src/new/Panel.ts b/packages/react-resizable-panels/src/new/Panel.ts new file mode 100644 index 000000000..8a0248958 --- /dev/null +++ b/packages/react-resizable-panels/src/new/Panel.ts @@ -0,0 +1,242 @@ +import { isDevelopment } from "../env-conditions/production"; +import useIsomorphicLayoutEffect from "../hooks/useIsomorphicEffect"; +import useUniqueId from "../hooks/useUniqueId"; +import { + ElementType, + ForwardedRef, + PropsWithChildren, + createElement, + forwardRef, + useContext, + useImperativeHandle, + useRef, +} from "../vendor/react"; +import { PanelGroupContext } from "./PanelGroupContext"; +import { MixedSizes } from "./types"; + +export type OnCollapse = () => void; +export type OnExpand = () => void; +export type OnResize = ({ + sizePercentage, + sizePixels, +}: { + sizePercentage: number; + sizePixels: number; +}) => void; + +export type PanelCallbacks = { + onCollapse?: OnCollapse; + onExpand?: OnExpand; + onResize?: OnResize; +}; + +export type PanelConstraints = { + collapsedSizePercentage?: number | undefined; + collapsedSizePixels?: number | undefined; + collapsible?: boolean | undefined; + defaultSizePercentage?: number | undefined; + defaultSizePixels?: number | undefined; + maxSizePercentage?: number | undefined; + maxSizePixels?: number | undefined; + minSizePercentage?: number | undefined; + minSizePixels?: number | undefined; +}; + +export type PanelData = { + callbacks: PanelCallbacks; + constraints: PanelConstraints; + id: string; + idIsFromProps: boolean; + order: number | undefined; +}; + +export type ImperativePanelHandle = { + collapse: () => void; + expand: () => void; + getId(): string; + getSize(): MixedSizes; + resize: (percentage: number) => void; +}; + +export type PanelProps = PropsWithChildren<{ + className?: string; + collapsedSizePercentage?: number | undefined; + collapsedSizePixels?: number | undefined; + collapsible?: boolean | undefined; + defaultSizePercentage?: number | undefined; + defaultSizePixels?: number | undefined; + id?: string; + maxSizePercentage?: number | undefined; + maxSizePixels?: number | undefined; + minSizePercentage?: number | undefined; + minSizePixels?: number | undefined; + onCollapse?: OnCollapse; + onExpand?: OnExpand; + onResize?: OnResize; + order?: number; + style?: object; + tagName?: ElementType; +}>; + +export function PanelWithForwardedRef({ + children, + className: classNameFromProps = "", + collapsedSizePercentage, + collapsedSizePixels, + collapsible, + defaultSizePercentage, + defaultSizePixels, + forwardedRef, + id: idFromProps, + maxSizePercentage, + maxSizePixels, + minSizePercentage, + minSizePixels, + onCollapse, + onExpand, + onResize, + order, + style: styleFromProps, + tagName: Type = "div", +}: PanelProps & { + forwardedRef: ForwardedRef; +}) { + const context = useContext(PanelGroupContext); + if (context === null) { + throw Error( + `Panel components must be rendered within a PanelGroup container` + ); + } + + const { + collapsePanel, + expandPanel, + getPanelSize, + getPanelStyle, + isPanelCollapsed, + registerPanel, + resizePanel, + unregisterPanel, + } = context; + + const panelId = useUniqueId(idFromProps); + + const panelDataRef = useRef({ + callbacks: { + onCollapse, + onExpand, + onResize, + }, + constraints: { + collapsedSizePercentage, + collapsedSizePixels, + collapsible, + defaultSizePercentage, + defaultSizePixels, + maxSizePercentage, + maxSizePixels, + minSizePercentage, + minSizePixels, + }, + id: panelId, + idIsFromProps: idFromProps !== undefined, + order, + }); + + useIsomorphicLayoutEffect(() => { + const { callbacks, constraints } = panelDataRef.current; + + panelDataRef.current.id = panelId; + panelDataRef.current.idIsFromProps = idFromProps !== undefined; + panelDataRef.current.order = order; + + callbacks.onCollapse = onCollapse; + callbacks.onExpand = onExpand; + callbacks.onResize = onResize; + + constraints.collapsedSizePercentage = collapsedSizePercentage; + constraints.collapsedSizePixels = collapsedSizePixels; + constraints.collapsible = collapsible; + constraints.defaultSizePercentage = defaultSizePercentage; + constraints.defaultSizePixels = defaultSizePixels; + constraints.maxSizePercentage = maxSizePercentage; + constraints.maxSizePixels = maxSizePixels; + constraints.minSizePercentage = minSizePercentage; + constraints.minSizePixels = minSizePixels; + }); + + useIsomorphicLayoutEffect(() => { + const panelData = panelDataRef.current; + + registerPanel(panelData); + + return () => { + unregisterPanel(panelData); + }; + }, [order, panelId, registerPanel, unregisterPanel]); + + useImperativeHandle( + forwardedRef, + () => ({ + collapse: () => { + collapsePanel(panelDataRef.current); + }, + expand: () => { + expandPanel(panelDataRef.current); + }, + getId() { + return panelId; + }, + getSize() { + return getPanelSize(panelDataRef.current); + }, + isCollapsed() { + return isPanelCollapsed(panelDataRef.current); + }, + isExpanded() { + return !isPanelCollapsed(panelDataRef.current); + }, + resize: (percentage: number) => + resizePanel(panelDataRef.current, percentage), + }), + [ + collapsePanel, + expandPanel, + getPanelSize, + isPanelCollapsed, + panelId, + resizePanel, + ] + ); + + const style = getPanelStyle(panelDataRef.current); + + return createElement(Type, { + children, + className: classNameFromProps, + style: { + ...style, + ...styleFromProps, + }, + + // CSS selectors + "data-panel": "", + + // e2e test attributes + "data-panel-collapsible": isDevelopment + ? collapsible || undefined + : undefined, + "data-panel-id": isDevelopment ? panelId : undefined, + "data-panel-size": isDevelopment + ? parseFloat("" + style.flexGrow).toFixed(1) + : undefined, + }); +} + +export const Panel = forwardRef( + (props: PanelProps, ref: ForwardedRef) => + createElement(PanelWithForwardedRef, { ...props, forwardedRef: ref }) +); + +PanelWithForwardedRef.displayName = "Panel"; +Panel.displayName = "forwardRef(Panel)"; diff --git a/packages/react-resizable-panels/src/new/PanelGroup.ts b/packages/react-resizable-panels/src/new/PanelGroup.ts new file mode 100644 index 000000000..948a6f66a --- /dev/null +++ b/packages/react-resizable-panels/src/new/PanelGroup.ts @@ -0,0 +1,591 @@ +import { isDevelopment } from "#is-development"; +import useIsomorphicLayoutEffect from "../hooks/useIsomorphicEffect"; +import useUniqueId from "../hooks/useUniqueId"; +import { resetGlobalCursorStyle, setGlobalCursorStyle } from "../utils/cursor"; +import debounce from "../utils/debounce"; +import { getAvailableGroupSizePixels, getResizeHandle } from "../utils/group"; +import { + CSSProperties, + ElementType, + PropsWithChildren, + createElement, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "../vendor/react"; +import { PanelData } from "./Panel"; +import { DragState, PanelGroupContext, ResizeEvent } from "./PanelGroupContext"; +import { Direction } from "./types"; +import { adjustLayoutByDelta } from "./utils/adjustLayoutByDelta"; +import { calculateDefaultLayout } from "./utils/calculateDefaultLayout"; +import { calculateDeltaPercentage } from "./utils/calculateDeltaPercentage"; +import { compareLayouts } from "./utils/compareLayouts"; +import { computePanelFlexBoxStyle } from "./utils/computePanelFlexBoxStyle"; +import { computePercentagePanelConstraints } from "./utils/computePercentagePanelConstraints"; +import { convertPercentageToPixels } from "./utils/convertPercentageToPixels"; +import { determinePivotIndices } from "./utils/determinePivotIndices"; +import { isMouseEvent, isTouchEvent } from "./utils/events"; +import { getResizeEventCursorPosition } from "./utils/getResizeEventCursorPosition"; +import { initializeDefaultStorage } from "./utils/initializeDefaultStorage"; +import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization"; +import { validatePanelGroupLayout } from "./utils/validatePanelGroupLayout"; + +// TODO Move group/DOM helpers into new package + +// TODO Use ResizeObserver (but only if any Panels declare pixels units) + +export type PanelGroupStorage = { + getItem(name: string): string | null; + setItem(name: string, value: string): void; +}; + +export type PanelGroupOnLayout = (sizes: number[]) => void; +export type PanelOnCollapse = (collapsed: boolean) => void; +export type PanelOnResize = (size: number, prevSize: number) => void; +export type PanelResizeHandleOnDragging = (isDragging: boolean) => void; + +const defaultStorage: PanelGroupStorage = { + getItem: (name: string) => { + initializeDefaultStorage(defaultStorage); + return defaultStorage.getItem(name); + }, + setItem: (name: string, value: string) => { + initializeDefaultStorage(defaultStorage); + defaultStorage.setItem(name, value); + }, +}; + +export type PanelGroupProps = PropsWithChildren<{ + autoSaveId?: string; + className?: string; + direction: Direction; + id?: string | null; + onLayout?: PanelGroupOnLayout; + storage?: PanelGroupStorage; + style?: CSSProperties; + tagName?: ElementType; +}>; + +const debounceMap: { + [key: string]: typeof savePanelGroupLayout; +} = {}; + +export function PanelGroup({ + autoSaveId, + children, + className: classNameFromProps = "", + direction, + id: idFromProps, + onLayout, + storage = defaultStorage, + style: styleFromProps, + tagName: Type = "div", +}: PanelGroupProps) { + const groupId = useUniqueId(idFromProps); + + const [dragState, setDragState] = useState(null); + const [layout, setLayout] = useState([]); + const [panelDataArray, setPanelDataArray] = useState([]); + + const prevDeltaRef = useRef(0); + + const committedValuesRef = useRef<{ + direction: Direction; + dragState: DragState | null; + id: string; + layout: number[]; + panelDataArray: PanelData[]; + }>({ + direction, + dragState, + id: groupId, + layout, + panelDataArray, + }); + + useIsomorphicLayoutEffect(() => { + committedValuesRef.current.direction = direction; + committedValuesRef.current.dragState = dragState; + committedValuesRef.current.id = groupId; + committedValuesRef.current.layout = layout; + committedValuesRef.current.panelDataArray = panelDataArray; + }); + + // TODO Dev warnings + + useEffect(() => { + // If this panel has been configured to persist sizing information, save sizes to local storage. + if (autoSaveId) { + if (layout.length === 0 || layout.length !== panelDataArray.length) { + return; + } + + // Limit the frequency of localStorage updates. + if (!debounceMap[autoSaveId]) { + debounceMap[autoSaveId] = debounce(savePanelGroupLayout, 100); + } + debounceMap[autoSaveId](autoSaveId, panelDataArray, layout, storage); + } + }, [autoSaveId, layout, panelDataArray, storage]); + + // Once all panels have registered themselves, + // Compute the initial sizes based on default weights. + // This assumes that panels register during initial mount (no conditional rendering)! + useIsomorphicLayoutEffect(() => { + const { id: groupId, layout } = committedValuesRef.current; + if (layout.length === panelDataArray.length) { + // Only compute (or restore) default layout once per panel configuration. + return; + } + + // If this panel has been configured to persist sizing information, + // default size should be restored from local storage if possible. + let unsafeLayout: number[] | null = null; + if (autoSaveId) { + unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage); + } + + const groupSizePixels = getAvailableGroupSizePixels(groupId); + + if (unsafeLayout == null) { + unsafeLayout = calculateDefaultLayout({ + groupSizePixels, + panelDataArray, + }); + } + + // Validate even saved layouts in case something has changed since last render + // e.g. for pixel groups, this could be the size of the window + const validatedLayout = validatePanelGroupLayout({ + groupSizePixels, + layout: unsafeLayout, + panelConstraints: panelDataArray.map( + (panelData) => panelData.constraints + ), + }); + + setLayout(validatedLayout); + }, [autoSaveId, layout, panelDataArray, storage]); + + // External APIs are safe to memoize via committed values ref + const collapsePanel = useCallback( + (panelData: PanelData) => { + const { layout, panelDataArray } = committedValuesRef.current; + + if (panelData.constraints.collapsible) { + const panelConstraintsArray = panelDataArray.map( + (panelData) => panelData.constraints + ); + + const { + collapsedSizePercentage, + panelSizePercentage, + pivotIndices, + groupSizePixels, + } = panelDataHelper(groupId, panelDataArray, panelData, layout); + + if (panelSizePercentage !== collapsedSizePercentage) { + // TODO Store size before collapse + + const nextLayout = adjustLayoutByDelta({ + delta: collapsedSizePercentage - panelSizePercentage, + groupSizePixels, + layout, + panelConstraints: panelConstraintsArray, + pivotIndices, + }); + + if (!compareLayouts(layout, nextLayout)) { + setLayout(nextLayout); + + // TODO Callbacks and stuff (put in helper function that takes prevLayout, nextLayout) + } + } + } + }, + [groupId] + ); + + // External APIs are safe to memoize via committed values ref + const expandPanel = useCallback( + (panelData: PanelData) => { + const { layout, panelDataArray } = committedValuesRef.current; + + if (panelData.constraints.collapsible) { + const panelConstraintsArray = panelDataArray.map( + (panelData) => panelData.constraints + ); + + const { + collapsedSizePercentage, + panelSizePercentage, + minSizePercentage, + pivotIndices, + groupSizePixels, + } = panelDataHelper(groupId, panelDataArray, panelData, layout); + + if (panelSizePercentage === collapsedSizePercentage) { + // TODO Retrieve size before collapse + + const nextLayout = adjustLayoutByDelta({ + delta: minSizePercentage - panelSizePercentage, + groupSizePixels, + layout, + panelConstraints: panelConstraintsArray, + pivotIndices, + }); + + if (!compareLayouts(layout, nextLayout)) { + setLayout(nextLayout); + + // TODO Callbacks and stuff (put in helper function that takes prevLayout, nextLayout) + } + } + } + }, + [groupId] + ); + + // External APIs are safe to memoize via committed values ref + const getPanelSize = useCallback( + (panelData: PanelData) => { + const { layout, panelDataArray } = committedValuesRef.current; + + const { panelSizePercentage, panelSizePixels } = panelDataHelper( + groupId, + panelDataArray, + panelData, + layout + ); + + return { + sizePercentage: panelSizePercentage, + sizePixels: panelSizePixels, + }; + }, + [groupId] + ); + + // This API should never read from committedValuesRef + const getPanelStyle = useCallback( + (panelData: PanelData) => { + const panelIndex = panelDataArray.indexOf(panelData); + + return computePanelFlexBoxStyle({ + dragState, + layout, + panelData: panelDataArray, + panelIndex, + }); + }, + [dragState, layout, panelDataArray] + ); + + // External APIs are safe to memoize via committed values ref + const isPanelCollapsed = useCallback( + (panelData: PanelData) => { + const { layout, panelDataArray } = committedValuesRef.current; + + const { collapsedSizePercentage, collapsible, panelSizePercentage } = + panelDataHelper(groupId, panelDataArray, panelData, layout); + + return ( + collapsible === true && panelSizePercentage === collapsedSizePercentage + ); + }, + [groupId] + ); + + // External APIs are safe to memoize via committed values ref + const isPanelExpanded = useCallback( + (panelData: PanelData) => { + const { layout, panelDataArray } = committedValuesRef.current; + + const { collapsedSizePercentage, collapsible, panelSizePercentage } = + panelDataHelper(groupId, panelDataArray, panelData, layout); + + return !collapsible || panelSizePercentage > collapsedSizePercentage; + }, + [groupId] + ); + + const registerPanel = useCallback((panelData: PanelData) => { + setPanelDataArray((prevPanelDataArray) => { + const nextPanelDataArray = [...prevPanelDataArray, panelData]; + return nextPanelDataArray.sort((panelA, panelB) => { + const orderA = panelA.order; + const orderB = panelB.order; + if (orderA == null && orderB == null) { + return 0; + } else if (orderA == null) { + return -1; + } else if (orderB == null) { + return 1; + } else { + return orderA - orderB; + } + }); + }); + }, []); + + const registerResizeHandle = useCallback((dragHandleId: string) => { + return function resizeHandler(event: ResizeEvent) { + event.preventDefault(); + + const { + direction, + dragState, + id: groupId, + panelDataArray, + layout: prevLayout, + } = committedValuesRef.current; + + const { initialLayout } = dragState ?? {}; + + const pivotIndices = determinePivotIndices(groupId, dragHandleId); + + let delta = calculateDeltaPercentage( + event, + groupId, + dragHandleId, + direction, + dragState! + ); + if (delta === 0) { + return; + } + + // Support RTL layouts + const isHorizontal = direction === "horizontal"; + if (document.dir === "rtl" && isHorizontal) { + delta = -delta; + } + + const groupSizePixels = getAvailableGroupSizePixels(groupId); + const panelConstraints = panelDataArray.map( + (panelData) => panelData.constraints + ); + + const nextLayout = adjustLayoutByDelta({ + collapsedPanelMode: "snap", + delta, + groupSizePixels, + layout: initialLayout ?? prevLayout, + panelConstraints, + pivotIndices, + }); + + const layoutChanged = !compareLayouts(prevLayout, nextLayout); + + // Only update the cursor for layout changes triggered by touch/mouse events (not keyboard) + // Update the cursor even if the layout hasn't changed (we may need to show an invalid cursor state) + if (isMouseEvent(event) || isTouchEvent(event)) { + // Watch for multiple subsequent deltas; this might occur for tiny cursor movements. + // In this case, Panel sizes might not change– + // but updating cursor in this scenario would cause a flicker. + if (prevDeltaRef.current != delta) { + prevDeltaRef.current = delta; + + if (!layoutChanged) { + // If the pointer has moved too far to resize the panel any further, + // update the cursor style for a visual clue. + // This mimics VS Code behavior. + + if (isHorizontal) { + setGlobalCursorStyle( + delta < 0 ? "horizontal-min" : "horizontal-max" + ); + } else { + setGlobalCursorStyle(delta < 0 ? "vertical-min" : "vertical-max"); + } + } else { + // Reset the cursor style to the the normal resize cursor. + setGlobalCursorStyle(isHorizontal ? "horizontal" : "vertical"); + } + } + } + + if (layoutChanged) { + setLayout(nextLayout); + + // TODO Callbacks and stuff (put in helper function that takes prevLayout, nextLayout) + } + }; + }, []); + + // External APIs are safe to memoize via committed values ref + const resizePanel = useCallback( + (panelData: PanelData, sizePercentage: number) => { + const { layout, panelDataArray } = committedValuesRef.current; + + const panelConstraintsArray = panelDataArray.map( + (panelData) => panelData.constraints + ); + + const { groupSizePixels, panelSizePercentage, pivotIndices } = + panelDataHelper(groupId, panelDataArray, panelData, layout); + + const nextLayout = adjustLayoutByDelta({ + delta: sizePercentage - panelSizePercentage, + groupSizePixels, + layout, + panelConstraints: panelConstraintsArray, + pivotIndices, + }); + + if (!compareLayouts(layout, nextLayout)) { + setLayout(nextLayout); + + // TODO Callbacks and stuff (put in helper function that takes prevLayout, nextLayout) + } + }, + [groupId] + ); + + const startDragging = useCallback( + (dragHandleId: string, event: ResizeEvent) => { + const { direction, layout } = committedValuesRef.current; + + const handleElement = getResizeHandle(dragHandleId)!; + + const initialCursorPosition = getResizeEventCursorPosition( + direction, + event + ); + + setDragState({ + dragHandleId, + dragHandleRect: handleElement.getBoundingClientRect(), + initialCursorPosition, + initialLayout: layout, + }); + }, + [] + ); + + const stopDragging = useCallback(() => { + resetGlobalCursorStyle(); + setDragState(null); + }, []); + + const unregisterPanel = useCallback((panelData: PanelData) => { + setPanelDataArray((panelDataArray) => { + const index = panelDataArray.indexOf(panelData); + if (index >= 0) { + panelDataArray = [...panelDataArray]; + panelDataArray.splice(index, 1); + } + + return panelDataArray; + }); + }, []); + + const context = useMemo( + () => ({ + collapsePanel, + direction, + dragState, + expandPanel, + getPanelSize, + getPanelStyle, + groupId, + isPanelCollapsed, + isPanelExpanded, + registerPanel, + registerResizeHandle, + resizePanel, + startDragging, + stopDragging, + unregisterPanel, + }), + [ + collapsePanel, + dragState, + direction, + expandPanel, + getPanelSize, + getPanelStyle, + groupId, + isPanelCollapsed, + isPanelExpanded, + registerPanel, + registerResizeHandle, + resizePanel, + startDragging, + stopDragging, + unregisterPanel, + ] + ); + + const style: CSSProperties = { + display: "flex", + flexDirection: direction === "horizontal" ? "row" : "column", + height: "100%", + overflow: "hidden", + width: "100%", + }; + + return createElement( + PanelGroupContext.Provider, + { value: context }, + createElement(Type, { + children, + className: classNameFromProps, + style: { + ...style, + ...styleFromProps, + }, + + // CSS selectors + "data-panel-group": "", + + // e2e test attributes + "data-panel-group-direction": isDevelopment ? direction : undefined, + "data-panel-group-id": isDevelopment ? groupId : undefined, + }) + ); +} + +function panelDataHelper( + groupId: string, + panelDataArray: PanelData[], + panelData: PanelData, + layout: number[] +) { + const panelConstraintsArray = panelDataArray.map( + (panelData) => panelData.constraints + ); + + const panelIndex = panelDataArray.indexOf(panelData); + const panelConstraints = panelConstraintsArray[panelIndex]; + + const groupSizePixels = getAvailableGroupSizePixels(groupId); + + const percentagePanelConstraints = computePercentagePanelConstraints( + panelConstraintsArray, + panelIndex, + groupSizePixels + ); + + const isLastPanel = panelIndex === panelDataArray.length - 1; + const pivotIndices = isLastPanel + ? [panelIndex - 1, panelIndex] + : [panelIndex, panelIndex + 1]; + + const panelSizePercentage = layout[panelIndex]; + const panelSizePixels = convertPercentageToPixels( + panelSizePercentage, + groupSizePixels + ); + + return { + ...percentagePanelConstraints, + collapsible: panelConstraints.collapsible, + panelSizePercentage, + panelSizePixels, + groupSizePixels, + pivotIndices, + }; +} diff --git a/packages/react-resizable-panels/src/new/PanelGroupContext.ts b/packages/react-resizable-panels/src/new/PanelGroupContext.ts new file mode 100644 index 000000000..649cda334 --- /dev/null +++ b/packages/react-resizable-panels/src/new/PanelGroupContext.ts @@ -0,0 +1,33 @@ +import { CSSProperties, createContext } from "../vendor/react"; +import { PanelData } from "./Panel"; +import { MixedSizes } from "./types"; + +export type ResizeEvent = KeyboardEvent | MouseEvent | TouchEvent; +export type ResizeHandler = (event: ResizeEvent) => void; + +export type DragState = { + dragHandleId: string; + dragHandleRect: DOMRect; + initialCursorPosition: number; + initialLayout: number[]; +}; + +export const PanelGroupContext = createContext<{ + collapsePanel: (panelData: PanelData) => void; + direction: "horizontal" | "vertical"; + dragState: DragState | null; + expandPanel: (panelData: PanelData) => void; + getPanelSize: (panelData: PanelData) => MixedSizes; + getPanelStyle: (panelData: PanelData) => CSSProperties; + groupId: string; + isPanelCollapsed: (panelData: PanelData) => boolean; + isPanelExpanded: (panelData: PanelData) => boolean; + registerPanel: (panelData: PanelData) => void; + registerResizeHandle: (dragHandleId: string) => ResizeHandler; + resizePanel: (panelData: PanelData, sizePercentage: number) => void; + startDragging: (dragHandleId: string, event: ResizeEvent) => void; + stopDragging: () => void; + unregisterPanel: (panelData: PanelData) => void; +} | null>(null); + +PanelGroupContext.displayName = "PanelGroupContext"; diff --git a/packages/react-resizable-panels/src/new/PanelResizeHandle.ts b/packages/react-resizable-panels/src/new/PanelResizeHandle.ts new file mode 100644 index 000000000..4e5f5e113 --- /dev/null +++ b/packages/react-resizable-panels/src/new/PanelResizeHandle.ts @@ -0,0 +1,193 @@ +import useUniqueId from "../hooks/useUniqueId"; +import { + createElement, + CSSProperties, + ElementType, + MouseEvent as ReactMouseEvent, + ReactNode, + TouchEvent, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from "../vendor/react"; + +import { useWindowSplitterResizeHandlerBehavior } from "../hooks/useWindowSplitterBehavior"; +import { getCursorStyle } from "../utils/cursor"; +import type { PanelResizeHandleOnDragging } from "./PanelGroup"; +import { + PanelGroupContext, + ResizeEvent, + ResizeHandler, +} from "./PanelGroupContext"; + +export type PanelResizeHandleProps = { + children?: ReactNode; + className?: string; + disabled?: boolean; + id?: string | null; + onDragging?: PanelResizeHandleOnDragging; + style?: CSSProperties; + tagName?: ElementType; +}; + +export function PanelResizeHandle({ + children = null, + className: classNameFromProps = "", + disabled = false, + id: idFromProps = null, + onDragging, + style: styleFromProps = {}, + tagName: Type = "div", +}: PanelResizeHandleProps) { + const divElementRef = useRef(null); + + // Use a ref to guard against users passing inline props + const callbacksRef = useRef<{ + onDragging: PanelResizeHandleOnDragging | undefined; + }>({ onDragging }); + useEffect(() => { + callbacksRef.current.onDragging = onDragging; + }); + + const panelGroupContext = useContext(PanelGroupContext); + if (panelGroupContext === null) { + throw Error( + `PanelResizeHandle components must be rendered within a PanelGroup container` + ); + } + + const { + direction, + dragState, + groupId, + registerResizeHandle, + startDragging, + stopDragging, + } = panelGroupContext; + + const resizeHandleId = useUniqueId(idFromProps); + const isDragging = dragState?.dragHandleId === resizeHandleId; + + const [isFocused, setIsFocused] = useState(false); + + const [resizeHandler, setResizeHandler] = useState( + null + ); + + const stopDraggingAndBlur = useCallback(() => { + // Clicking on the drag handle shouldn't leave it focused; + // That would cause the PanelGroup to think it was still active. + const div = divElementRef.current!; + div.blur(); + + stopDragging(); + + const { onDragging } = callbacksRef.current; + if (onDragging) { + onDragging(false); + } + }, [stopDragging]); + + useEffect(() => { + if (disabled) { + setResizeHandler(null); + } else { + const resizeHandler = registerResizeHandle(resizeHandleId); + setResizeHandler(() => resizeHandler); + } + }, [disabled, resizeHandleId, registerResizeHandle]); + + useEffect(() => { + if (disabled || resizeHandler == null || !isDragging) { + return; + } + + const onMove = (event: ResizeEvent) => { + resizeHandler(event); + }; + + const onMouseLeave = (event: MouseEvent) => { + resizeHandler(event); + }; + + const divElement = divElementRef.current!; + const targetDocument = divElement.ownerDocument; + + targetDocument.body.addEventListener("contextmenu", stopDraggingAndBlur); + targetDocument.body.addEventListener("mousemove", onMove); + targetDocument.body.addEventListener("touchmove", onMove); + targetDocument.body.addEventListener("mouseleave", onMouseLeave); + window.addEventListener("mouseup", stopDraggingAndBlur); + window.addEventListener("touchend", stopDraggingAndBlur); + + return () => { + targetDocument.body.removeEventListener( + "contextmenu", + stopDraggingAndBlur + ); + targetDocument.body.removeEventListener("mousemove", onMove); + targetDocument.body.removeEventListener("touchmove", onMove); + targetDocument.body.removeEventListener("mouseleave", onMouseLeave); + window.removeEventListener("mouseup", stopDraggingAndBlur); + window.removeEventListener("touchend", stopDraggingAndBlur); + }; + }, [direction, disabled, isDragging, resizeHandler, stopDraggingAndBlur]); + + useWindowSplitterResizeHandlerBehavior({ + disabled, + handleId: resizeHandleId, + resizeHandler, + }); + + const style: CSSProperties = { + cursor: getCursorStyle(direction), + touchAction: "none", + userSelect: "none", + }; + + return createElement(Type, { + children, + className: classNameFromProps, + "data-resize-handle-active": isDragging + ? "pointer" + : isFocused + ? "keyboard" + : undefined, + "data-panel-group-direction": direction, + "data-panel-group-id": groupId, + "data-panel-resize-handle-enabled": !disabled, + "data-panel-resize-handle-id": resizeHandleId, + onBlur: () => setIsFocused(false), + onFocus: () => setIsFocused(true), + onMouseDown: (event: ReactMouseEvent) => { + startDragging(resizeHandleId, event.nativeEvent); + + const { onDragging } = callbacksRef.current!; + if (onDragging) { + onDragging(true); + } + }, + onMouseUp: stopDraggingAndBlur, + onTouchCancel: stopDraggingAndBlur, + onTouchEnd: stopDraggingAndBlur, + onTouchStart: (event: TouchEvent) => { + startDragging(resizeHandleId, event.nativeEvent); + + const { onDragging } = callbacksRef.current!; + if (onDragging) { + onDragging(true); + } + }, + ref: divElementRef, + role: "separator", + style: { + ...style, + ...styleFromProps, + }, + tabIndex: 0, + }); +} + +PanelResizeHandle.displayName = "PanelResizeHandle"; diff --git a/packages/react-resizable-panels/src/new/types.ts b/packages/react-resizable-panels/src/new/types.ts new file mode 100644 index 000000000..9b4cbe4c8 --- /dev/null +++ b/packages/react-resizable-panels/src/new/types.ts @@ -0,0 +1,6 @@ +export type Direction = "horizontal" | "vertical"; + +export type MixedSizes = { + sizePercentage: number; + sizePixels: number; +}; diff --git a/packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.test.ts b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.test.ts similarity index 85% rename from packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.test.ts rename to packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.test.ts index 7f6d39291..8a2ff6642 100644 --- a/packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.test.ts +++ b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.test.ts @@ -5,7 +5,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 1, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [{}, {}], pivotIndices: [0, 1], @@ -17,7 +17,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [{}, {}], pivotIndices: [0, 1], @@ -26,7 +26,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [{}, {}], pivotIndices: [0, 1], @@ -38,7 +38,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [ { @@ -59,7 +59,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [ {}, @@ -78,7 +78,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 30, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [ {}, @@ -112,11 +112,113 @@ describe("adjustLayoutByDelta", () => { ).toEqual([25, 75]); }); + // Edge case + // Expanding from a collapsed state to less than the min size + it("[1++,2]", () => { + // collapsed 4%, min size 6%, max size 15% + expect( + adjustLayoutByDelta({ + delta: 1, + groupSizePixels: 1_000, + layout: [4, 96], + panelConstraints: [ + { + collapsedSizePixels: 40, + collapsible: true, + defaultSizePixels: 150, + maxSizePixels: 150, + minSizePixels: 60, + }, + { + minSizePercentage: 50, + }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([6, 94]); + }); + + // Edge case + // Expanding from a collapsed state to greater than the max size + it("[1++,2]", () => { + // collapsed 4%, min size 6%, max size 15% + expect( + adjustLayoutByDelta({ + delta: 25, + groupSizePixels: 1_000, + layout: [4, 96], + panelConstraints: [ + { + collapsedSizePixels: 40, + collapsible: true, + defaultSizePixels: 150, + maxSizePixels: 150, + minSizePixels: 60, + }, + { + minSizePercentage: 50, + }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([15, 85]); + }); + + // Edge case + // Expanding from a collapsed state to a valid size in "permissive" (default) mode + it("[1++,2]", () => { + expect( + adjustLayoutByDelta({ + collapsedPanelMode: "permissive", + delta: 30, + groupSizePixels: 100, + layout: [5, 95], + panelConstraints: [ + { + collapsedSizePixels: 5, + collapsible: true, + maxSizePixels: 50, + minSizePixels: 25, + }, + { + minSizePercentage: 50, + }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([35, 65]); + }); + + // Edge case + // Expanding from a collapsed state to a valid size in "snap" mode + it("[1++,2]", () => { + expect( + adjustLayoutByDelta({ + collapsedPanelMode: "snap", + delta: 30, + groupSizePixels: 100, + layout: [5, 95], + panelConstraints: [ + { + collapsedSizePixels: 5, + collapsible: true, + maxSizePixels: 50, + minSizePixels: 25, + }, + { + minSizePercentage: 50, + }, + ], + pivotIndices: [0, 1], + }) + ).toEqual([25, 75]); + }); + it("[1--,2]", () => { expect( adjustLayoutByDelta({ delta: -1, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [{}, {}], pivotIndices: [0, 1], @@ -128,7 +230,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [{}, {}], pivotIndices: [0, 1], @@ -140,7 +242,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [{}, {}], pivotIndices: [0, 1], @@ -152,7 +254,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [ { @@ -173,7 +275,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [ { @@ -192,7 +294,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -30, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [ { @@ -213,7 +315,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -30, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [50, 50], panelConstraints: [ { @@ -232,7 +334,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 1, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [0, 1], @@ -244,7 +346,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [0, 1], @@ -256,7 +358,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [0, 1], @@ -268,7 +370,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 75, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [0, 1], @@ -280,7 +382,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [ { maxSizePercentage: 35 }, @@ -297,7 +399,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [ { maxSizePercentage: 35 }, @@ -313,7 +415,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 5, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 40, 35], panelConstraints: [ {}, @@ -333,7 +435,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 26, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 40, 35], panelConstraints: [ {}, @@ -353,7 +455,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 80, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 40, 35], panelConstraints: [ {}, @@ -373,7 +475,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -1, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [0, 1], @@ -385,7 +487,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [0, 1], @@ -397,7 +499,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -1, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{ minSizePercentage: 20 }, {}, {}], pivotIndices: [0, 1], @@ -409,7 +511,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{ minSizePercentage: 20 }, {}, {}], pivotIndices: [0, 1], @@ -421,7 +523,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -5, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [ { @@ -439,7 +541,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -20, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [ { @@ -457,7 +559,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [ { @@ -477,7 +579,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -20, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [ { @@ -497,7 +599,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -20, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [45, 50, 5], panelConstraints: [ {}, @@ -519,7 +621,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -1, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [1, 2], @@ -531,7 +633,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [1, 2], @@ -543,7 +645,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [1, 2], @@ -555,7 +657,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -75, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [1, 2], @@ -567,7 +669,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 5, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, { minSizePercentage: 15 }], pivotIndices: [1, 2], @@ -579,7 +681,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 20, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, { minSizePercentage: 15 }], pivotIndices: [1, 2], @@ -591,7 +693,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 5, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [ {}, @@ -607,7 +709,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [ {}, @@ -623,7 +725,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 1, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [1, 2], @@ -635,7 +737,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [1, 2], @@ -647,7 +749,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -20, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, { minSizePercentage: 40 }, {}], pivotIndices: [1, 2], @@ -659,7 +761,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [{}, {}, { maxSizePercentage: 30 }], pivotIndices: [1, 2], @@ -671,7 +773,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -35, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [ {}, @@ -691,7 +793,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -60, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 50, 25], panelConstraints: [ { @@ -711,7 +813,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 1, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [0, 1], @@ -723,7 +825,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [0, 1], @@ -735,7 +837,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [0, 1], @@ -747,7 +849,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 75, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [0, 1], @@ -759,7 +861,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{ maxSizePercentage: 35 }, {}, {}, {}], pivotIndices: [0, 1], @@ -771,7 +873,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 100, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ {}, @@ -788,7 +890,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ {}, @@ -817,7 +919,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 40, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ {}, @@ -846,7 +948,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 100, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ {}, @@ -875,7 +977,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -1, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [0, 1], @@ -887,7 +989,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [0, 1], @@ -899,7 +1001,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{ minSizePercentage: 20 }, {}, {}, {}], pivotIndices: [0, 1], @@ -911,7 +1013,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, { maxSizePercentage: 35 }, {}, {}], pivotIndices: [0, 1], @@ -923,7 +1025,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ { @@ -944,7 +1046,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ { @@ -971,7 +1073,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ { @@ -998,7 +1100,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ { @@ -1019,7 +1121,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ { @@ -1040,7 +1142,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [1, 2], @@ -1052,7 +1154,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 30, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [1, 2], @@ -1064,7 +1166,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [1, 2], @@ -1076,7 +1178,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, { maxSizePercentage: 35 }, {}, {}], pivotIndices: [1, 2], @@ -1088,7 +1190,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, { minSizePercentage: 20 }, {}], pivotIndices: [1, 2], @@ -1100,7 +1202,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ {}, @@ -1121,7 +1223,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 30, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ {}, @@ -1142,7 +1244,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ {}, @@ -1163,7 +1265,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -25, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [1, 2], @@ -1175,7 +1277,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [1, 2], @@ -1187,7 +1289,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, { minSizePercentage: 20 }, {}, {}], pivotIndices: [1, 2], @@ -1199,7 +1301,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{ minSizePercentage: 20 }, {}, {}, {}], pivotIndices: [1, 2], @@ -1211,7 +1313,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ { minSizePercentage: 20 }, @@ -1228,7 +1330,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -5, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ { @@ -1249,7 +1351,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ { @@ -1270,7 +1372,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ {}, @@ -1291,7 +1393,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [2, 3], @@ -1303,7 +1405,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 30, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [2, 3], @@ -1315,7 +1417,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 30, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, { maxSizePercentage: 40 }, {}], pivotIndices: [2, 3], @@ -1327,7 +1429,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 30, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, { minSizePercentage: 10 }], pivotIndices: [2, 3], @@ -1339,7 +1441,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 5, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ {}, @@ -1360,7 +1462,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: 50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ {}, @@ -1381,7 +1483,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -10, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [2, 3], @@ -1393,7 +1495,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -40, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [2, 3], @@ -1405,7 +1507,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -100, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [2, 3], @@ -1417,7 +1519,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ { minSizePercentage: 10 }, @@ -1434,7 +1536,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, { maxSizePercentage: 40 }], pivotIndices: [2, 3], @@ -1446,7 +1548,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -50, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [{}, { minSizePercentage: 5 }, {}, {}], pivotIndices: [2, 3], @@ -1458,7 +1560,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -100, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ { @@ -1487,7 +1589,7 @@ describe("adjustLayoutByDelta", () => { expect( adjustLayoutByDelta({ delta: -100, - groupSizePixels: NaN, + groupSizePixels: 100, layout: [25, 25, 25, 25], panelConstraints: [ { @@ -1510,5 +1612,7 @@ describe("adjustLayoutByDelta", () => { // TODO expandToSize after collapsed + // TODO Infinite loop check + // TODO Invalid pixel constraints (min too large, max too small) should recover by recomputing base? }); diff --git a/packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.ts b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts similarity index 59% rename from packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.ts rename to packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts index 0132d4122..baa26f039 100644 --- a/packages/react-resizable-panels/src/utils/new/adjustLayoutByDelta.ts +++ b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts @@ -1,27 +1,33 @@ import { computePercentagePanelConstraints } from "./computePercentagePanelConstraints"; -import { fuzzyCompareNumbers } from "./fuzzyCompareNumbers"; +import { fuzzyNumbersEqual } from "./fuzzyNumbersEqual"; import { resizePanel } from "./resizePanel"; -import { PanelConstraints } from "./types"; +import { PanelConstraints } from "../Panel"; +import { fuzzyCompareNumbers } from "./fuzzyCompareNumbers"; + +let isCheckingForInfiniteLoop = false; // All units must be in percentages; pixel values should be pre-converted export function adjustLayoutByDelta({ + collapsedPanelMode = "permissive", delta, groupSizePixels, - layout, + layout: prevLayout, panelConstraints, pivotIndices, }: { + collapsedPanelMode?: "permissive" | "snap"; delta: number; groupSizePixels: number; layout: number[]; panelConstraints: PanelConstraints[]; - // TODO panelExpandToSizes: number[] pivotIndices: number[]; }): number[] { - if (delta === 0) { - return layout; + if (fuzzyNumbersEqual(delta, 0)) { + return prevLayout; } + const nextLayout = [...prevLayout]; + let deltaApplied = 0; // A resizing panel affects the panels before or after it. @@ -36,21 +42,30 @@ export function adjustLayoutByDelta({ // We should only expand or contract by as much as its constraints allow { const pivotIndex = delta < 0 ? pivotIndices[1]! : pivotIndices[0]!; - const initialSize = layout[pivotIndex]!; + const initialSize = nextLayout[pivotIndex]!; const { collapsible } = panelConstraints[pivotIndex]!; - const { collapsedSizePercentage, minSizePercentage } = + const { collapsedSizePercentage, maxSizePercentage, minSizePercentage } = computePercentagePanelConstraints( panelConstraints, pivotIndex, groupSizePixels ); - const isCollapsed = collapsible && initialSize === collapsedSizePercentage; - // TODO Could this, combined with the recursion, cause an infinite loop? - const unsafeSize = isCollapsed - ? minSizePercentage - : initialSize + Math.abs(delta); + const isCollapsed = + collapsible && fuzzyNumbersEqual(initialSize, collapsedSizePercentage); + + let unsafeSize = initialSize + Math.abs(delta); + if (isCollapsed) { + if (fuzzyCompareNumbers(unsafeSize, maxSizePercentage) > 0) { + unsafeSize = maxSizePercentage; + } else if (fuzzyCompareNumbers(unsafeSize, minSizePercentage) < 0) { + unsafeSize = minSizePercentage; + } else if (collapsedPanelMode === "snap") { + unsafeSize = minSizePercentage; + } + } + const safeSize = resizePanel({ groupSizePixels, panelConstraints, @@ -58,16 +73,14 @@ export function adjustLayoutByDelta({ size: unsafeSize, }); - if (initialSize === safeSize) { + if (fuzzyNumbersEqual(initialSize, safeSize)) { // If there's no room for the pivot panel to grow, we should ignore this change - return layout; + return nextLayout; } else { delta = delta < 0 ? initialSize - safeSize : safeSize - initialSize; } } - const initialLayout = [...layout]; - // Delta added to a panel needs to be subtracted from other panels // within the constraints that those panels allow { @@ -76,8 +89,8 @@ export function adjustLayoutByDelta({ while (index >= 0 && index < panelConstraints.length) { const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied); - const baseSize = initialLayout[index]!; - const unsafeSize = baseSize - deltaRemaining; + const prevSize = prevLayout[index]!; + const unsafeSize = prevSize - deltaRemaining; let safeSize = resizePanel({ groupSizePixels, @@ -86,10 +99,10 @@ export function adjustLayoutByDelta({ size: unsafeSize, }); - if (baseSize !== safeSize) { - deltaApplied += baseSize - safeSize; + if (!fuzzyNumbersEqual(prevSize, safeSize)) { + deltaApplied += prevSize - safeSize; - layout[index] = safeSize; + nextLayout[index] = safeSize; if ( deltaApplied @@ -112,14 +125,14 @@ export function adjustLayoutByDelta({ // If we were unable to resize any of the panels panels, return the previous state. // This will essentially bailout and ignore e.g. drags past a panel's boundaries - if (fuzzyCompareNumbers(deltaApplied, 0)) { - return initialLayout; + if (fuzzyNumbersEqual(deltaApplied, 0)) { + return prevLayout; } { const pivotIndex = delta < 0 ? pivotIndices[1]! : pivotIndices[0]!; - const unsafeSize = initialLayout[pivotIndex]! + deltaApplied; + const unsafeSize = prevLayout[pivotIndex]! + deltaApplied; const safeSize = resizePanel({ groupSizePixels, panelConstraints, @@ -128,17 +141,17 @@ export function adjustLayoutByDelta({ }); // Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract. - layout[pivotIndex] = safeSize; + nextLayout[pivotIndex] = safeSize; // Edge case where expanding or contracting one panel caused another one to change collapsed state - if (safeSize !== unsafeSize) { + if (!fuzzyNumbersEqual(safeSize, unsafeSize)) { let deltaRemaining = unsafeSize - safeSize; const pivotIndex = delta < 0 ? pivotIndices[1]! : pivotIndices[0]!; let index = pivotIndex; while (index >= 0 && index < panelConstraints.length) { - const baseSize = layout[index]!; - const unsafeSize = baseSize + deltaRemaining; + const prevSize = nextLayout[index]!; + const unsafeSize = prevSize + deltaRemaining; const safeSize = resizePanel({ groupSizePixels, panelConstraints, @@ -146,13 +159,13 @@ export function adjustLayoutByDelta({ size: unsafeSize, }); - if (baseSize !== safeSize) { - deltaRemaining -= safeSize - baseSize; + if (!fuzzyNumbersEqual(prevSize, safeSize)) { + deltaRemaining -= safeSize - prevSize; - layout[index] = safeSize; + nextLayout[index] = safeSize; } - if (fuzzyCompareNumbers(deltaRemaining, 0)) { + if (fuzzyNumbersEqual(deltaRemaining, 0)) { break; } @@ -165,17 +178,35 @@ export function adjustLayoutByDelta({ // If we can't redistribute, this layout is invalid; // There may be an incremental layout that is valid though - if (!fuzzyCompareNumbers(deltaRemaining, 0)) { - return adjustLayoutByDelta({ - delta: delta < 0 ? delta + 1 : delta - 1, - groupSizePixels, - layout: initialLayout, - panelConstraints, - pivotIndices, - }); + if (!fuzzyNumbersEqual(deltaRemaining, 0)) { + let didSetInfiniteLoopCheckCounter = false; + if (isCheckingForInfiniteLoop === null) { + didSetInfiniteLoopCheckCounter = true; + isCheckingForInfiniteLoop = true; + } + + try { + return adjustLayoutByDelta({ + delta: delta < 0 ? delta + 1 : delta - 1, + groupSizePixels, + layout: prevLayout, + panelConstraints, + pivotIndices, + }); + } catch (error) { + if (error instanceof RangeError) { + console.error(`Could not apply delta ${delta} to layout`); + + return prevLayout; + } + } finally { + if (didSetInfiniteLoopCheckCounter) { + isCheckingForInfiniteLoop = false; + } + } } } } - return layout; + return nextLayout; } diff --git a/packages/react-resizable-panels/src/new/utils/calculateDefaultLayout.ts b/packages/react-resizable-panels/src/new/utils/calculateDefaultLayout.ts new file mode 100644 index 000000000..615facffa --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/calculateDefaultLayout.ts @@ -0,0 +1,57 @@ +import { PanelData } from "../Panel"; +import { computePercentagePanelConstraints } from "./computePercentagePanelConstraints"; + +// TODO Add basic unit tests for this + +export function calculateDefaultLayout({ + groupSizePixels, + panelDataArray, +}: { + groupSizePixels: number; + panelDataArray: PanelData[]; +}): number[] { + const layout = Array(panelDataArray.length); + + const panelDataConstraints = panelDataArray.map( + (panelData) => panelData.constraints + ); + + let numPanelsWithSizes = 0; + let remainingSize = 100; + + // Distribute default sizes first + for (let index = 0; index < panelDataArray.length; index++) { + const { defaultSizePercentage } = computePercentagePanelConstraints( + panelDataConstraints, + index, + groupSizePixels + ); + + if (defaultSizePercentage != null) { + numPanelsWithSizes++; + layout[index] = defaultSizePercentage; + remainingSize -= defaultSizePercentage; + } + } + + // Remaining size should be distributed evenly between panels without default sizes + for (let index = 0; index < panelDataArray.length; index++) { + const { defaultSizePercentage } = computePercentagePanelConstraints( + panelDataConstraints, + index, + groupSizePixels + ); + if (defaultSizePercentage != null) { + continue; + } + + const numRemainingPanels = panelDataArray.length - numPanelsWithSizes; + const size = remainingSize / numRemainingPanels; + + numPanelsWithSizes++; + layout[index] = size; + remainingSize -= size; + } + + return layout; +} diff --git a/packages/react-resizable-panels/src/new/utils/calculateDeltaPercentage.ts b/packages/react-resizable-panels/src/new/utils/calculateDeltaPercentage.ts new file mode 100644 index 000000000..0f785c9f9 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/calculateDeltaPercentage.ts @@ -0,0 +1,56 @@ +import { getPanelGroup } from "../../utils/group"; +import { DragState, ResizeEvent } from "../PanelGroupContext"; +import { Direction } from "../types"; +import { isKeyDown } from "./events"; +import { calculateDragOffsetPercentage } from "./calculateDragOffsetPercentage"; + +// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX +export function calculateDeltaPercentage( + event: ResizeEvent, + groupId: string, + dragHandleId: string, + direction: Direction, + initialDragState: DragState +): number { + if (isKeyDown(event)) { + const isHorizontal = direction === "horizontal"; + + const groupElement = getPanelGroup(groupId)!; + const rect = groupElement.getBoundingClientRect(); + const groupSizeInPixels = isHorizontal ? rect.width : rect.height; + + const denominator = event.shiftKey ? 10 : 100; + const delta = groupSizeInPixels / denominator; + + let movement = 0; + switch (event.key) { + case "ArrowDown": + movement = isHorizontal ? 0 : delta; + break; + case "ArrowLeft": + movement = isHorizontal ? -delta : 0; + break; + case "ArrowRight": + movement = isHorizontal ? delta : 0; + break; + case "ArrowUp": + movement = isHorizontal ? 0 : -delta; + break; + case "End": + movement = groupSizeInPixels; + break; + case "Home": + movement = -groupSizeInPixels; + break; + } + + return movement; + } else { + return calculateDragOffsetPercentage( + event, + dragHandleId, + direction, + initialDragState + ); + } +} diff --git a/packages/react-resizable-panels/src/new/utils/calculateDragOffsetPercentage.ts b/packages/react-resizable-panels/src/new/utils/calculateDragOffsetPercentage.ts new file mode 100644 index 000000000..3c3b9dbc4 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/calculateDragOffsetPercentage.ts @@ -0,0 +1,29 @@ +import { getPanelGroup, getResizeHandle } from "../../utils/group"; +import { DragState, ResizeEvent } from "../PanelGroupContext"; +import { Direction } from "../types"; +import { getResizeEventCursorPosition } from "./getResizeEventCursorPosition"; + +export function calculateDragOffsetPercentage( + event: ResizeEvent, + dragHandleId: string, + direction: Direction, + initialDragState: DragState +): number { + const isHorizontal = direction === "horizontal"; + + const handleElement = getResizeHandle(dragHandleId)!; + const groupId = handleElement.getAttribute("data-panel-group-id")!; + + let { initialCursorPosition } = initialDragState; + + const cursorPosition = getResizeEventCursorPosition(direction, event); + + const groupElement = getPanelGroup(groupId)!; + const groupRect = groupElement.getBoundingClientRect(); + const groupSizeInPixels = isHorizontal ? groupRect.width : groupRect.height; + + const offsetPixels = cursorPosition - initialCursorPosition; + const offsetPercentage = (offsetPixels / groupSizeInPixels) * 100; + + return offsetPercentage; +} diff --git a/packages/react-resizable-panels/src/utils/new/compareLayouts.test.ts b/packages/react-resizable-panels/src/new/utils/compareLayouts.test.ts similarity index 100% rename from packages/react-resizable-panels/src/utils/new/compareLayouts.test.ts rename to packages/react-resizable-panels/src/new/utils/compareLayouts.test.ts diff --git a/packages/react-resizable-panels/src/utils/new/compareLayouts.ts b/packages/react-resizable-panels/src/new/utils/compareLayouts.ts similarity index 100% rename from packages/react-resizable-panels/src/utils/new/compareLayouts.ts rename to packages/react-resizable-panels/src/new/utils/compareLayouts.ts diff --git a/packages/react-resizable-panels/src/new/utils/computePanelFlexBoxStyle.ts b/packages/react-resizable-panels/src/new/utils/computePanelFlexBoxStyle.ts new file mode 100644 index 000000000..3380e6050 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/computePanelFlexBoxStyle.ts @@ -0,0 +1,44 @@ +// This method returns a number between 1 and 100 representing + +import { CSSProperties } from "../../vendor/react"; +import { PanelData } from "../Panel"; +import { DragState } from "../PanelGroupContext"; + +// the % of the group's overall space this panel should occupy. +export function computePanelFlexBoxStyle({ + dragState, + layout, + panelData, + panelIndex, + precision = 3, +}: { + layout: number[]; + dragState: DragState | null; + panelData: PanelData[]; + panelIndex: number; + precision?: number; +}): CSSProperties { + const size = layout[panelIndex]; + + let flexGrow; + if (panelData.length === 1) { + flexGrow = "100"; + } else if (size == null) { + flexGrow = "0"; + } else { + flexGrow = size.toPrecision(precision); + } + + return { + flexBasis: 0, + flexGrow, + flexShrink: 1, + + // Without this, Panel sizes may be unintentionally overridden by their content + overflow: "hidden", + + // Disable pointer events inside of a panel during resize + // This avoid edge cases like nested iframes + pointerEvents: dragState !== null ? "none" : undefined, + }; +} diff --git a/packages/react-resizable-panels/src/utils/new/computePercentagePanelConstraints.ts b/packages/react-resizable-panels/src/new/utils/computePercentagePanelConstraints.ts similarity index 54% rename from packages/react-resizable-panels/src/utils/new/computePercentagePanelConstraints.ts rename to packages/react-resizable-panels/src/new/utils/computePercentagePanelConstraints.ts index de0ce12c0..3014d3531 100644 --- a/packages/react-resizable-panels/src/utils/new/computePercentagePanelConstraints.ts +++ b/packages/react-resizable-panels/src/new/utils/computePercentagePanelConstraints.ts @@ -1,5 +1,5 @@ -import { convertPixelsToPercentage } from "./convertPixelsToPercentage"; -import { PanelConstraints } from "./types"; +import { PanelConstraints } from "../Panel"; +import { convertPixelConstraintsToPercentages } from "./convertPixelConstraintsToPercentages"; export function computePercentagePanelConstraints( panelConstraintsArray: PanelConstraints[], @@ -54,56 +54,3 @@ export function computePercentagePanelConstraints( : minSizePercentage, }; } - -export function convertPixelConstraintsToPercentages( - panelConstraints: PanelConstraints, - groupSizePixels: number -): { - collapsedSizePercentage: number; - defaultSizePercentage: number | undefined; - maxSizePercentage: number; - minSizePercentage: number; -} { - let { - collapsedSizePercentage = 0, - collapsedSizePixels, - defaultSizePercentage, - defaultSizePixels, - maxSizePercentage = 100, - maxSizePixels, - minSizePercentage = 0, - minSizePixels, - } = panelConstraints; - - if (collapsedSizePixels != null) { - collapsedSizePercentage = convertPixelsToPercentage( - collapsedSizePixels, - groupSizePixels - ); - } - if (defaultSizePixels != null) { - defaultSizePercentage = convertPixelsToPercentage( - defaultSizePixels, - groupSizePixels - ); - } - if (minSizePixels != null) { - minSizePercentage = convertPixelsToPercentage( - minSizePixels, - groupSizePixels - ); - } - if (maxSizePixels != null) { - maxSizePercentage = convertPixelsToPercentage( - maxSizePixels, - groupSizePixels - ); - } - - return { - collapsedSizePercentage, - defaultSizePercentage, - maxSizePercentage, - minSizePercentage, - }; -} diff --git a/packages/react-resizable-panels/src/utils/new/convertPercentageToPixels.test.ts b/packages/react-resizable-panels/src/new/utils/convertPercentageToPixels.test.ts similarity index 100% rename from packages/react-resizable-panels/src/utils/new/convertPercentageToPixels.test.ts rename to packages/react-resizable-panels/src/new/utils/convertPercentageToPixels.test.ts diff --git a/packages/react-resizable-panels/src/utils/new/convertPercentageToPixels.ts b/packages/react-resizable-panels/src/new/utils/convertPercentageToPixels.ts similarity index 100% rename from packages/react-resizable-panels/src/utils/new/convertPercentageToPixels.ts rename to packages/react-resizable-panels/src/new/utils/convertPercentageToPixels.ts diff --git a/packages/react-resizable-panels/src/new/utils/convertPixelConstraintsToPercentages.ts b/packages/react-resizable-panels/src/new/utils/convertPixelConstraintsToPercentages.ts new file mode 100644 index 000000000..d4348f3ff --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/convertPixelConstraintsToPercentages.ts @@ -0,0 +1,55 @@ +import { PanelConstraints } from "../Panel"; +import { convertPixelsToPercentage } from "./convertPixelsToPercentage"; + +export function convertPixelConstraintsToPercentages( + panelConstraints: PanelConstraints, + groupSizePixels: number +): { + collapsedSizePercentage: number; + defaultSizePercentage: number | undefined; + maxSizePercentage: number; + minSizePercentage: number; +} { + let { + collapsedSizePercentage = 0, + collapsedSizePixels, + defaultSizePercentage, + defaultSizePixels, + maxSizePercentage = 100, + maxSizePixels, + minSizePercentage = 0, + minSizePixels, + } = panelConstraints; + + if (collapsedSizePixels != null) { + collapsedSizePercentage = convertPixelsToPercentage( + collapsedSizePixels, + groupSizePixels + ); + } + if (defaultSizePixels != null) { + defaultSizePercentage = convertPixelsToPercentage( + defaultSizePixels, + groupSizePixels + ); + } + if (minSizePixels != null) { + minSizePercentage = convertPixelsToPercentage( + minSizePixels, + groupSizePixels + ); + } + if (maxSizePixels != null) { + maxSizePercentage = convertPixelsToPercentage( + maxSizePixels, + groupSizePixels + ); + } + + return { + collapsedSizePercentage, + defaultSizePercentage, + maxSizePercentage, + minSizePercentage, + }; +} diff --git a/packages/react-resizable-panels/src/utils/new/convertPixelsToPercentage.test.ts b/packages/react-resizable-panels/src/new/utils/convertPixelsToPercentage.test.ts similarity index 100% rename from packages/react-resizable-panels/src/utils/new/convertPixelsToPercentage.test.ts rename to packages/react-resizable-panels/src/new/utils/convertPixelsToPercentage.test.ts diff --git a/packages/react-resizable-panels/src/utils/new/convertPixelsToPercentage.ts b/packages/react-resizable-panels/src/new/utils/convertPixelsToPercentage.ts similarity index 100% rename from packages/react-resizable-panels/src/utils/new/convertPixelsToPercentage.ts rename to packages/react-resizable-panels/src/new/utils/convertPixelsToPercentage.ts diff --git a/packages/react-resizable-panels/src/new/utils/determinePivotIndices.ts b/packages/react-resizable-panels/src/new/utils/determinePivotIndices.ts new file mode 100644 index 000000000..ccba40360 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/determinePivotIndices.ts @@ -0,0 +1,12 @@ +import { getResizeHandle, getResizeHandlesForGroup } from "../../utils/group"; + +export function determinePivotIndices( + groupId: string, + dragHandleId: string +): [indexBefore: number, indexAfter: number] { + const handle = getResizeHandle(dragHandleId); + const handles = getResizeHandlesForGroup(groupId); + const index = handle ? handles.indexOf(handle) : -1; + + return [index, index + 1]; +} diff --git a/packages/react-resizable-panels/src/new/utils/events.ts b/packages/react-resizable-panels/src/new/utils/events.ts new file mode 100644 index 000000000..7e87d2d58 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/events.ts @@ -0,0 +1,13 @@ +import { ResizeEvent } from "../PanelGroupContext"; + +export function isKeyDown(event: ResizeEvent): event is KeyboardEvent { + return event.type === "keydown"; +} + +export function isMouseEvent(event: ResizeEvent): event is MouseEvent { + return event.type.startsWith("mouse"); +} + +export function isTouchEvent(event: ResizeEvent): event is TouchEvent { + return event.type.startsWith("touch"); +} diff --git a/packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.test.ts b/packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.test.ts new file mode 100644 index 000000000..d2702ff4f --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.test.ts @@ -0,0 +1,16 @@ +import { fuzzyCompareNumbers } from "./fuzzyCompareNumbers"; + +describe("fuzzyCompareNumbers", () => { + it("should return 0 when numbers are equal", () => { + expect(fuzzyCompareNumbers(10.123, 10.123, 5)).toBe(0); + }); + + it("should return 0 when numbers are fuzzy equal", () => { + expect(fuzzyCompareNumbers(0.000001, 0.000002, 5)).toBe(0); + }); + + it("should return a delta when numbers are not unequal", () => { + expect(fuzzyCompareNumbers(0.000001, 0.000002, 6)).toBe(-1); + expect(fuzzyCompareNumbers(0.000005, 0.000002, 6)).toBe(1); + }); +}); diff --git a/packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.ts b/packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.ts new file mode 100644 index 000000000..5fe4b7a8e --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.ts @@ -0,0 +1,17 @@ +import { PRECISION } from "../../constants"; + +export function fuzzyCompareNumbers( + actual: number, + expected: number, + fractionDigits: number = PRECISION +): number { + actual = parseFloat(actual.toFixed(fractionDigits)); + expected = parseFloat(expected.toFixed(fractionDigits)); + + const delta = actual - expected; + if (delta === 0) { + return 0; + } else { + return delta > 0 ? 1 : -1; + } +} diff --git a/packages/react-resizable-panels/src/new/utils/fuzzyNumbersEqual.ts b/packages/react-resizable-panels/src/new/utils/fuzzyNumbersEqual.ts new file mode 100644 index 000000000..c464cf5a0 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/fuzzyNumbersEqual.ts @@ -0,0 +1,9 @@ +import { fuzzyCompareNumbers } from "./fuzzyCompareNumbers"; + +export function fuzzyNumbersEqual( + actual: number, + expected: number, + fractionDigits?: number +): boolean { + return fuzzyCompareNumbers(actual, expected, fractionDigits) === 0; +} diff --git a/packages/react-resizable-panels/src/new/utils/getResizeEventCursorPosition.ts b/packages/react-resizable-panels/src/new/utils/getResizeEventCursorPosition.ts new file mode 100644 index 000000000..888fd2939 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/getResizeEventCursorPosition.ts @@ -0,0 +1,19 @@ +import { ResizeEvent } from "../PanelGroupContext"; +import { Direction } from "../types"; +import { isMouseEvent, isTouchEvent } from "./events"; + +export function getResizeEventCursorPosition( + direction: Direction, + event: ResizeEvent +): number { + const isHorizontal = direction === "horizontal"; + + if (isMouseEvent(event)) { + return isHorizontal ? event.clientX : event.clientY; + } else if (isTouchEvent(event)) { + const firstTouch = event.touches[0]; + return isHorizontal ? firstTouch.screenX : firstTouch.screenY; + } else { + throw Error(`Unsupported event type "${event.type}"`); + } +} diff --git a/packages/react-resizable-panels/src/new/utils/initializeDefaultStorage.ts b/packages/react-resizable-panels/src/new/utils/initializeDefaultStorage.ts new file mode 100644 index 000000000..901fa33d2 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/initializeDefaultStorage.ts @@ -0,0 +1,26 @@ +import { PanelGroupStorage } from "../PanelGroup"; + +// PanelGroup might be rendering in a server-side environment where localStorage is not available +// or on a browser with cookies/storage disabled. +// In either case, this function avoids accessing localStorage until needed, +// and avoids throwing user-visible errors. +export function initializeDefaultStorage(storageObject: PanelGroupStorage) { + try { + if (typeof localStorage !== "undefined") { + // Bypass this check for future calls + storageObject.getItem = (name: string) => { + return localStorage.getItem(name); + }; + storageObject.setItem = (name: string, value: string) => { + localStorage.setItem(name, value); + }; + } else { + throw new Error("localStorage not supported in this environment"); + } + } catch (error) { + console.error(error); + + storageObject.getItem = () => null; + storageObject.setItem = () => {}; + } +} diff --git a/packages/react-resizable-panels/src/utils/new/resizePanel.ts b/packages/react-resizable-panels/src/new/utils/resizePanel.ts similarity index 80% rename from packages/react-resizable-panels/src/utils/new/resizePanel.ts rename to packages/react-resizable-panels/src/new/utils/resizePanel.ts index a2b523deb..5b24ae438 100644 --- a/packages/react-resizable-panels/src/utils/new/resizePanel.ts +++ b/packages/react-resizable-panels/src/new/utils/resizePanel.ts @@ -1,5 +1,7 @@ +import { PRECISION } from "../../constants"; +import { PanelConstraints } from "../Panel"; import { computePercentagePanelConstraints } from "./computePercentagePanelConstraints"; -import { PanelConstraints } from "./types"; +import { fuzzyCompareNumbers } from "./fuzzyCompareNumbers"; // Panel size must be in percentages; pixel values should be pre-converted export function resizePanel({ @@ -23,7 +25,7 @@ export function resizePanel({ ); if (minSizePercentage != null) { - if (size < minSizePercentage) { + if (fuzzyCompareNumbers(size, minSizePercentage) < 0) { if (collapsible) { size = collapsedSizePercentage; } else { diff --git a/packages/react-resizable-panels/src/new/utils/serialization.ts b/packages/react-resizable-panels/src/new/utils/serialization.ts new file mode 100644 index 000000000..cbecc4d74 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/serialization.ts @@ -0,0 +1,70 @@ +import { PanelData } from "../Panel"; +import { PanelGroupStorage } from "../PanelGroup"; + +type SerializedPanelGroupState = { [panelIds: string]: number[] }; + +// Note that Panel ids might be user-provided (stable) or useId generated (non-deterministic) +// so they should not be used as part of the serialization key. +// Using the min/max size attributes should work well enough as a backup. +// Pre-sorting by minSize allows remembering layouts even if panels are re-ordered/dragged. +function getSerializationKey(panels: PanelData[]): string { + return panels + .map((panel) => { + const { id, idIsFromProps, constraints } = panel; + if (idIsFromProps) { + return id; + } else { + return JSON.stringify(constraints); + } + }) + .sort((a, b) => a.localeCompare(b)) + .join(","); +} + +function loadSerializedPanelGroupState( + autoSaveId: string, + storage: PanelGroupStorage +): SerializedPanelGroupState | null { + try { + const serialized = storage.getItem(`PanelGroup:sizes:${autoSaveId}`); + if (serialized) { + const parsed = JSON.parse(serialized); + if (typeof parsed === "object" && parsed != null) { + return parsed; + } + } + } catch (error) {} + + return null; +} + +export function loadPanelLayout( + autoSaveId: string, + panels: PanelData[], + storage: PanelGroupStorage +): number[] | null { + const state = loadSerializedPanelGroupState(autoSaveId, storage); + if (state) { + const key = getSerializationKey(panels); + return state[key] ?? null; + } + + return null; +} + +export function savePanelGroupLayout( + autoSaveId: string, + panels: PanelData[], + sizes: number[], + storage: PanelGroupStorage +): void { + const key = getSerializationKey(panels); + const state = loadSerializedPanelGroupState(autoSaveId, storage) || {}; + state[key] = sizes; + + try { + storage.setItem(`PanelGroup:sizes:${autoSaveId}`, JSON.stringify(state)); + } catch (error) { + console.error(error); + } +} diff --git a/packages/react-resizable-panels/src/utils/new/test-utils.ts b/packages/react-resizable-panels/src/new/utils/test-utils.ts similarity index 100% rename from packages/react-resizable-panels/src/utils/new/test-utils.ts rename to packages/react-resizable-panels/src/new/utils/test-utils.ts diff --git a/packages/react-resizable-panels/src/utils/new/validatePanelConstraints.test.ts b/packages/react-resizable-panels/src/new/utils/validatePanelConstraints.test.ts similarity index 100% rename from packages/react-resizable-panels/src/utils/new/validatePanelConstraints.test.ts rename to packages/react-resizable-panels/src/new/utils/validatePanelConstraints.test.ts diff --git a/packages/react-resizable-panels/src/utils/new/validatePanelConstraints.ts b/packages/react-resizable-panels/src/new/utils/validatePanelConstraints.ts similarity index 98% rename from packages/react-resizable-panels/src/utils/new/validatePanelConstraints.ts rename to packages/react-resizable-panels/src/new/utils/validatePanelConstraints.ts index cb7aeb337..3d27a8007 100644 --- a/packages/react-resizable-panels/src/utils/new/validatePanelConstraints.ts +++ b/packages/react-resizable-panels/src/new/utils/validatePanelConstraints.ts @@ -1,6 +1,6 @@ import { isDevelopment } from "#is-development"; import { computePercentagePanelConstraints } from "./computePercentagePanelConstraints"; -import { PanelConstraints } from "./types"; +import { PanelConstraints } from "../Panel"; export function validatePanelConstraints({ panelConstraints, diff --git a/packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.test.ts b/packages/react-resizable-panels/src/new/utils/validatePanelGroupLayout.test.ts similarity index 100% rename from packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.test.ts rename to packages/react-resizable-panels/src/new/utils/validatePanelGroupLayout.test.ts diff --git a/packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.ts b/packages/react-resizable-panels/src/new/utils/validatePanelGroupLayout.ts similarity index 68% rename from packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.ts rename to packages/react-resizable-panels/src/new/utils/validatePanelGroupLayout.ts index 94c9bd8e4..93f0a8658 100644 --- a/packages/react-resizable-panels/src/utils/new/validatePanelGroupLayout.ts +++ b/packages/react-resizable-panels/src/new/utils/validatePanelGroupLayout.ts @@ -1,32 +1,34 @@ -import { fuzzyCompareNumbers } from "./fuzzyCompareNumbers"; +import { fuzzyNumbersEqual } from "./fuzzyNumbersEqual"; import { resizePanel } from "./resizePanel"; -import { PanelConstraints } from "./types"; +import { PanelConstraints } from "../Panel"; // All units must be in percentages; pixel values should be pre-converted export function validatePanelGroupLayout({ groupSizePixels, - layout, + layout: prevLayout, panelConstraints, }: { groupSizePixels: number; layout: number[]; panelConstraints: PanelConstraints[]; }): number[] { + const nextLayout = [...prevLayout]; + // Validate layout expectations - if (layout.length !== panelConstraints.length) { + if (nextLayout.length !== panelConstraints.length) { throw Error( - `Invalid ${panelConstraints.length} panel layout: ${layout + `Invalid ${panelConstraints.length} panel layout: ${nextLayout .map((size) => `${size}%`) .join(", ")}` ); } else if ( - !fuzzyCompareNumbers( - layout.reduce((accumulated, current) => accumulated + current, 0), + !fuzzyNumbersEqual( + nextLayout.reduce((accumulated, current) => accumulated + current, 0), 100 ) ) { throw Error( - `Invalid layout total size: ${layout + `Invalid layout total size: ${nextLayout .map((size) => `${size}%`) .join(", ")}` ); @@ -36,7 +38,7 @@ export function validatePanelGroupLayout({ // First pass: Validate the proposed layout given each panel's constraints for (let index = 0; index < panelConstraints.length; index++) { - const unsafeSize = layout[index]!; + const unsafeSize = nextLayout[index]!; const safeSize = resizePanel({ groupSizePixels, @@ -48,15 +50,15 @@ export function validatePanelGroupLayout({ if (unsafeSize != safeSize) { remainingSize += unsafeSize - safeSize; - layout[index] = safeSize; + nextLayout[index] = safeSize; } } // If there is additional, left over space, assign it to any panel(s) that permits it // (It's not worth taking multiple additional passes to evenly distribute) - if (!fuzzyCompareNumbers(remainingSize, 0)) { + if (!fuzzyNumbersEqual(remainingSize, 0)) { for (let index = 0; index < panelConstraints.length; index++) { - const prevSize = layout[index]!; + const prevSize = nextLayout[index]!; const unsafeSize = prevSize + remainingSize; const safeSize = resizePanel({ groupSizePixels, @@ -67,15 +69,15 @@ export function validatePanelGroupLayout({ if (prevSize !== safeSize) { remainingSize -= safeSize - prevSize; - layout[index] = safeSize; + nextLayout[index] = safeSize; // Once we've used up the remainder, bail - if (fuzzyCompareNumbers(remainingSize, 0)) { + if (fuzzyNumbersEqual(remainingSize, 0)) { break; } } } } - return layout; + return nextLayout; } diff --git a/packages/react-resizable-panels/src/utils/new/fuzzyCompareNumbers.ts b/packages/react-resizable-panels/src/utils/new/fuzzyCompareNumbers.ts deleted file mode 100644 index ae65efd26..000000000 --- a/packages/react-resizable-panels/src/utils/new/fuzzyCompareNumbers.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function fuzzyCompareNumbers( - actual: number, - expected: number, - fractionDigits: number = 3 -) { - return actual.toFixed(fractionDigits) === expected.toFixed(fractionDigits); -} diff --git a/packages/react-resizable-panels/src/utils/new/types.ts b/packages/react-resizable-panels/src/utils/new/types.ts deleted file mode 100644 index 9ba6c3658..000000000 --- a/packages/react-resizable-panels/src/utils/new/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type PanelConstraints = { - collapsedSizePercentage?: number | undefined; - collapsedSizePixels?: number | undefined; - collapsible?: boolean | undefined; - defaultSizePercentage?: number | undefined; - defaultSizePixels?: number | undefined; - maxSizePercentage?: number | undefined; - maxSizePixels?: number | undefined; - minSizePercentage?: number | undefined; - minSizePixels?: number | undefined; -}; diff --git a/packages/react-resizable-panels/src/vendor/react.ts b/packages/react-resizable-panels/src/vendor/react.ts index 2f0467e04..6af4e3d2a 100644 --- a/packages/react-resizable-panels/src/vendor/react.ts +++ b/packages/react-resizable-panels/src/vendor/react.ts @@ -13,6 +13,7 @@ import type { ElementType, ForwardedRef, MouseEvent, + PropsWithChildren, ReactNode, RefObject, TouchEvent, @@ -55,6 +56,7 @@ export type { ElementType, ForwardedRef, MouseEvent, + PropsWithChildren, ReactNode, RefObject, TouchEvent, diff --git a/tsconfig.json b/tsconfig.json index 57d5168e2..8f3ccad11 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "esModuleInterop": true, "jsx": "react-jsx", "lib": ["ES2015", "DOM"], "module": "es2020", From 9b4246da9b4113b8e57e7251bc17ae5b73de8ac3 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 27 Aug 2023 17:45:44 -0400 Subject: [PATCH 06/35] Fixed ESLint config linkage to TSConfig file --- packages/react-resizable-panels/.eslintrc.cjs | 28 +++++++++++++++++++ .../react-resizable-panels/.eslintrc.json | 22 --------------- .../src/new/PanelGroup.ts | 11 ++++---- .../src/new/utils/calculateDeltaPercentage.ts | 6 ++-- .../utils/calculateDragOffsetPercentage.ts | 7 +++-- .../src/new/utils/determinePivotIndices.ts | 8 ++---- 6 files changed, 44 insertions(+), 38 deletions(-) create mode 100644 packages/react-resizable-panels/.eslintrc.cjs delete mode 100644 packages/react-resizable-panels/.eslintrc.json diff --git a/packages/react-resizable-panels/.eslintrc.cjs b/packages/react-resizable-panels/.eslintrc.cjs new file mode 100644 index 000000000..262714c7c --- /dev/null +++ b/packages/react-resizable-panels/.eslintrc.cjs @@ -0,0 +1,28 @@ +const { join } = require("path"); + +/* eslint-env node */ +module.exports = { + ignorePatterns: [".parcel-cache", "dist", "node_modules"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: "../../tsconfig.json", + tsconfigRootDir: __dirname, + }, + plugins: ["@typescript-eslint", "no-restricted-imports", "react-hooks"], + root: true, + rules: { + "no-restricted-imports": [ + "error", + { + paths: ["react"], + }, + ], + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": [ + "warn", + { + additionalHooks: "(useIsomorphicLayoutEffect)", + }, + ], + }, +}; diff --git a/packages/react-resizable-panels/.eslintrc.json b/packages/react-resizable-panels/.eslintrc.json deleted file mode 100644 index a63abb222..000000000 --- a/packages/react-resizable-panels/.eslintrc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "ignorePatterns": [".parcel-cache", "dist", "node_modules"], - "parser": "@typescript-eslint/parser", - "parserOptions": { "project": ["tsconfig.json"] }, - "plugins": ["@typescript-eslint", "no-restricted-imports", "react-hooks"], - "root": true, - "rules": { - "no-restricted-imports": [ - "error", - { - "paths": ["react"] - } - ], - "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": [ - "warn", - { - "additionalHooks": "(useIsomorphicLayoutEffect)" - } - ] - } -} diff --git a/packages/react-resizable-panels/src/new/PanelGroup.ts b/packages/react-resizable-panels/src/new/PanelGroup.ts index 948a6f66a..3e510a029 100644 --- a/packages/react-resizable-panels/src/new/PanelGroup.ts +++ b/packages/react-resizable-panels/src/new/PanelGroup.ts @@ -3,7 +3,6 @@ import useIsomorphicLayoutEffect from "../hooks/useIsomorphicEffect"; import useUniqueId from "../hooks/useUniqueId"; import { resetGlobalCursorStyle, setGlobalCursorStyle } from "../utils/cursor"; import debounce from "../utils/debounce"; -import { getAvailableGroupSizePixels, getResizeHandle } from "../utils/group"; import { CSSProperties, ElementType, @@ -26,6 +25,8 @@ import { computePanelFlexBoxStyle } from "./utils/computePanelFlexBoxStyle"; import { computePercentagePanelConstraints } from "./utils/computePercentagePanelConstraints"; import { convertPercentageToPixels } from "./utils/convertPercentageToPixels"; import { determinePivotIndices } from "./utils/determinePivotIndices"; +import { calculateAvailablePanelSizeInPixels } from "./utils/dom/calculateAvailablePanelSizeInPixels"; +import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement"; import { isMouseEvent, isTouchEvent } from "./utils/events"; import { getResizeEventCursorPosition } from "./utils/getResizeEventCursorPosition"; import { initializeDefaultStorage } from "./utils/initializeDefaultStorage"; @@ -147,7 +148,7 @@ export function PanelGroup({ unsafeLayout = loadPanelLayout(autoSaveId, panelDataArray, storage); } - const groupSizePixels = getAvailableGroupSizePixels(groupId); + const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId); if (unsafeLayout == null) { unsafeLayout = calculateDefaultLayout({ @@ -363,7 +364,7 @@ export function PanelGroup({ delta = -delta; } - const groupSizePixels = getAvailableGroupSizePixels(groupId); + const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId); const panelConstraints = panelDataArray.map( (panelData) => panelData.constraints ); @@ -448,7 +449,7 @@ export function PanelGroup({ (dragHandleId: string, event: ResizeEvent) => { const { direction, layout } = committedValuesRef.current; - const handleElement = getResizeHandle(dragHandleId)!; + const handleElement = getResizeHandleElement(dragHandleId)!; const initialCursorPosition = getResizeEventCursorPosition( direction, @@ -561,7 +562,7 @@ function panelDataHelper( const panelIndex = panelDataArray.indexOf(panelData); const panelConstraints = panelConstraintsArray[panelIndex]; - const groupSizePixels = getAvailableGroupSizePixels(groupId); + const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId); const percentagePanelConstraints = computePercentagePanelConstraints( panelConstraintsArray, diff --git a/packages/react-resizable-panels/src/new/utils/calculateDeltaPercentage.ts b/packages/react-resizable-panels/src/new/utils/calculateDeltaPercentage.ts index 0f785c9f9..bb6246eec 100644 --- a/packages/react-resizable-panels/src/new/utils/calculateDeltaPercentage.ts +++ b/packages/react-resizable-panels/src/new/utils/calculateDeltaPercentage.ts @@ -1,8 +1,8 @@ -import { getPanelGroup } from "../../utils/group"; import { DragState, ResizeEvent } from "../PanelGroupContext"; import { Direction } from "../types"; -import { isKeyDown } from "./events"; import { calculateDragOffsetPercentage } from "./calculateDragOffsetPercentage"; +import { getPanelGroupElement } from "./dom/getPanelGroupElement"; +import { isKeyDown } from "./events"; // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX export function calculateDeltaPercentage( @@ -15,7 +15,7 @@ export function calculateDeltaPercentage( if (isKeyDown(event)) { const isHorizontal = direction === "horizontal"; - const groupElement = getPanelGroup(groupId)!; + const groupElement = getPanelGroupElement(groupId)!; const rect = groupElement.getBoundingClientRect(); const groupSizeInPixels = isHorizontal ? rect.width : rect.height; diff --git a/packages/react-resizable-panels/src/new/utils/calculateDragOffsetPercentage.ts b/packages/react-resizable-panels/src/new/utils/calculateDragOffsetPercentage.ts index 3c3b9dbc4..681d366e6 100644 --- a/packages/react-resizable-panels/src/new/utils/calculateDragOffsetPercentage.ts +++ b/packages/react-resizable-panels/src/new/utils/calculateDragOffsetPercentage.ts @@ -1,6 +1,7 @@ -import { getPanelGroup, getResizeHandle } from "../../utils/group"; import { DragState, ResizeEvent } from "../PanelGroupContext"; import { Direction } from "../types"; +import { getPanelGroupElement } from "./dom/getPanelGroupElement"; +import { getResizeHandleElement } from "./dom/getResizeHandleElement"; import { getResizeEventCursorPosition } from "./getResizeEventCursorPosition"; export function calculateDragOffsetPercentage( @@ -11,14 +12,14 @@ export function calculateDragOffsetPercentage( ): number { const isHorizontal = direction === "horizontal"; - const handleElement = getResizeHandle(dragHandleId)!; + const handleElement = getResizeHandleElement(dragHandleId)!; const groupId = handleElement.getAttribute("data-panel-group-id")!; let { initialCursorPosition } = initialDragState; const cursorPosition = getResizeEventCursorPosition(direction, event); - const groupElement = getPanelGroup(groupId)!; + const groupElement = getPanelGroupElement(groupId)!; const groupRect = groupElement.getBoundingClientRect(); const groupSizeInPixels = isHorizontal ? groupRect.width : groupRect.height; diff --git a/packages/react-resizable-panels/src/new/utils/determinePivotIndices.ts b/packages/react-resizable-panels/src/new/utils/determinePivotIndices.ts index ccba40360..e002fb901 100644 --- a/packages/react-resizable-panels/src/new/utils/determinePivotIndices.ts +++ b/packages/react-resizable-panels/src/new/utils/determinePivotIndices.ts @@ -1,12 +1,10 @@ -import { getResizeHandle, getResizeHandlesForGroup } from "../../utils/group"; +import { getResizeHandleElementIndex } from "./dom/getResizeHandleElementIndex"; export function determinePivotIndices( groupId: string, dragHandleId: string ): [indexBefore: number, indexAfter: number] { - const handle = getResizeHandle(dragHandleId); - const handles = getResizeHandlesForGroup(groupId); - const index = handle ? handles.indexOf(handle) : -1; + const index = getResizeHandleElementIndex(groupId, dragHandleId); - return [index, index + 1]; + return index != null ? [index, index + 1] : [-1, -1]; } From 15bb5c5d4c76b1cd9a542ce4dbb9ff582cb069d5 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 27 Aug 2023 17:47:44 -0400 Subject: [PATCH 07/35] Reorgnanized util files --- packages/react-resizable-panels/.eslintrc.cjs | 2 -- .../src/new/utils/adjustLayoutByDelta.ts | 4 +-- .../calculateAvailablePanelSizeInPixels.ts | 29 +++++++++++++++++++ .../src/new/utils/dom/getPanelElement.ts | 7 +++++ .../src/new/utils/dom/getPanelGroupElement.ts | 7 +++++ .../new/utils/dom/getResizeHandleElement.ts | 9 ++++++ .../utils/dom/getResizeHandleElementIndex.ts | 12 ++++++++ .../new/utils/dom/getResizeHandleElements,ts | 3 ++ .../dom/getResizeHandleElementsForGroup.ts | 9 ++++++ .../{ => numbers}/fuzzyCompareNumbers.test.ts | 0 .../{ => numbers}/fuzzyCompareNumbers.ts | 2 +- .../utils/{ => numbers}/fuzzyNumbersEqual.ts | 0 .../src/new/utils/resizePanel.ts | 2 +- .../src/new/utils/validatePanelGroupLayout.ts | 2 +- 14 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 packages/react-resizable-panels/src/new/utils/dom/calculateAvailablePanelSizeInPixels.ts create mode 100644 packages/react-resizable-panels/src/new/utils/dom/getPanelElement.ts create mode 100644 packages/react-resizable-panels/src/new/utils/dom/getPanelGroupElement.ts create mode 100644 packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElement.ts create mode 100644 packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElementIndex.ts create mode 100644 packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElements,ts create mode 100644 packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElementsForGroup.ts rename packages/react-resizable-panels/src/new/utils/{ => numbers}/fuzzyCompareNumbers.test.ts (100%) rename packages/react-resizable-panels/src/new/utils/{ => numbers}/fuzzyCompareNumbers.ts (88%) rename packages/react-resizable-panels/src/new/utils/{ => numbers}/fuzzyNumbersEqual.ts (100%) diff --git a/packages/react-resizable-panels/.eslintrc.cjs b/packages/react-resizable-panels/.eslintrc.cjs index 262714c7c..860b1b47e 100644 --- a/packages/react-resizable-panels/.eslintrc.cjs +++ b/packages/react-resizable-panels/.eslintrc.cjs @@ -1,5 +1,3 @@ -const { join } = require("path"); - /* eslint-env node */ module.exports = { ignorePatterns: [".parcel-cache", "dist", "node_modules"], diff --git a/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts index baa26f039..8caf1eb86 100644 --- a/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts +++ b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts @@ -1,8 +1,8 @@ import { computePercentagePanelConstraints } from "./computePercentagePanelConstraints"; -import { fuzzyNumbersEqual } from "./fuzzyNumbersEqual"; +import { fuzzyNumbersEqual } from "./numbers/fuzzyNumbersEqual"; import { resizePanel } from "./resizePanel"; import { PanelConstraints } from "../Panel"; -import { fuzzyCompareNumbers } from "./fuzzyCompareNumbers"; +import { fuzzyCompareNumbers } from "./numbers/fuzzyCompareNumbers"; let isCheckingForInfiniteLoop = false; diff --git a/packages/react-resizable-panels/src/new/utils/dom/calculateAvailablePanelSizeInPixels.ts b/packages/react-resizable-panels/src/new/utils/dom/calculateAvailablePanelSizeInPixels.ts new file mode 100644 index 000000000..584254722 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/dom/calculateAvailablePanelSizeInPixels.ts @@ -0,0 +1,29 @@ +import { getPanelGroupElement } from "./getPanelGroupElement"; +import { getResizeHandleElementsForGroup } from "./getResizeHandleElementsForGroup"; + +export function calculateAvailablePanelSizeInPixels(groupId: string): number { + const panelGroupElement = getPanelGroupElement(groupId); + if (panelGroupElement == null) { + return NaN; + } + + const direction = panelGroupElement.getAttribute( + "data-panel-group-direction" + ); + const resizeHandles = getResizeHandleElementsForGroup(groupId); + if (direction === "horizontal") { + return ( + panelGroupElement.offsetWidth - + resizeHandles.reduce((accumulated, handle) => { + return accumulated + handle.offsetWidth; + }, 0) + ); + } else { + return ( + panelGroupElement.offsetHeight - + resizeHandles.reduce((accumulated, handle) => { + return accumulated + handle.offsetHeight; + }, 0) + ); + } +} diff --git a/packages/react-resizable-panels/src/new/utils/dom/getPanelElement.ts b/packages/react-resizable-panels/src/new/utils/dom/getPanelElement.ts new file mode 100644 index 000000000..f29df5efd --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/dom/getPanelElement.ts @@ -0,0 +1,7 @@ +export function getPanelElement(id: string): HTMLDivElement | null { + const element = document.querySelector(`[data-panel-id="${id}"]`); + if (element) { + return element as HTMLDivElement; + } + return null; +} diff --git a/packages/react-resizable-panels/src/new/utils/dom/getPanelGroupElement.ts b/packages/react-resizable-panels/src/new/utils/dom/getPanelGroupElement.ts new file mode 100644 index 000000000..835fe8466 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/dom/getPanelGroupElement.ts @@ -0,0 +1,7 @@ +export function getPanelGroupElement(id: string): HTMLDivElement | null { + const element = document.querySelector(`[data-panel-group-id="${id}"]`); + if (element) { + return element as HTMLDivElement; + } + return null; +} diff --git a/packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElement.ts b/packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElement.ts new file mode 100644 index 000000000..de7292cb8 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElement.ts @@ -0,0 +1,9 @@ +export function getResizeHandleElement(id: string): HTMLDivElement | null { + const element = document.querySelector( + `[data-panel-resize-handle-id="${id}"]` + ); + if (element) { + return element as HTMLDivElement; + } + return null; +} diff --git a/packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElementIndex.ts b/packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElementIndex.ts new file mode 100644 index 000000000..ae2c819a9 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElementIndex.ts @@ -0,0 +1,12 @@ +import { getResizeHandleElementsForGroup } from "./getResizeHandleElementsForGroup"; + +export function getResizeHandleElementIndex( + groupId: string, + id: string +): number | null { + const handles = getResizeHandleElementsForGroup(groupId); + const index = handles.findIndex( + (handle) => handle.getAttribute("data-panel-resize-handle-id") === id + ); + return index ?? null; +} diff --git a/packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElements,ts b/packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElements,ts new file mode 100644 index 000000000..f3ee99fb6 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElements,ts @@ -0,0 +1,3 @@ +export function getResizeHandleElements(): HTMLDivElement[] { + return Array.from(document.querySelectorAll(`[data-panel-resize-handle-id]`)); +} \ No newline at end of file diff --git a/packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElementsForGroup.ts b/packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElementsForGroup.ts new file mode 100644 index 000000000..24ba1c492 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/dom/getResizeHandleElementsForGroup.ts @@ -0,0 +1,9 @@ +export function getResizeHandleElementsForGroup( + groupId: string +): HTMLDivElement[] { + return Array.from( + document.querySelectorAll( + `[data-panel-resize-handle-id][data-panel-group-id="${groupId}"]` + ) + ); +} diff --git a/packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.test.ts b/packages/react-resizable-panels/src/new/utils/numbers/fuzzyCompareNumbers.test.ts similarity index 100% rename from packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.test.ts rename to packages/react-resizable-panels/src/new/utils/numbers/fuzzyCompareNumbers.test.ts diff --git a/packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.ts b/packages/react-resizable-panels/src/new/utils/numbers/fuzzyCompareNumbers.ts similarity index 88% rename from packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.ts rename to packages/react-resizable-panels/src/new/utils/numbers/fuzzyCompareNumbers.ts index 5fe4b7a8e..9137b5dd7 100644 --- a/packages/react-resizable-panels/src/new/utils/fuzzyCompareNumbers.ts +++ b/packages/react-resizable-panels/src/new/utils/numbers/fuzzyCompareNumbers.ts @@ -1,4 +1,4 @@ -import { PRECISION } from "../../constants"; +import { PRECISION } from "../../../constants"; export function fuzzyCompareNumbers( actual: number, diff --git a/packages/react-resizable-panels/src/new/utils/fuzzyNumbersEqual.ts b/packages/react-resizable-panels/src/new/utils/numbers/fuzzyNumbersEqual.ts similarity index 100% rename from packages/react-resizable-panels/src/new/utils/fuzzyNumbersEqual.ts rename to packages/react-resizable-panels/src/new/utils/numbers/fuzzyNumbersEqual.ts diff --git a/packages/react-resizable-panels/src/new/utils/resizePanel.ts b/packages/react-resizable-panels/src/new/utils/resizePanel.ts index 5b24ae438..2d007ad1e 100644 --- a/packages/react-resizable-panels/src/new/utils/resizePanel.ts +++ b/packages/react-resizable-panels/src/new/utils/resizePanel.ts @@ -1,7 +1,7 @@ import { PRECISION } from "../../constants"; import { PanelConstraints } from "../Panel"; import { computePercentagePanelConstraints } from "./computePercentagePanelConstraints"; -import { fuzzyCompareNumbers } from "./fuzzyCompareNumbers"; +import { fuzzyCompareNumbers } from "./numbers/fuzzyCompareNumbers"; // Panel size must be in percentages; pixel values should be pre-converted export function resizePanel({ diff --git a/packages/react-resizable-panels/src/new/utils/validatePanelGroupLayout.ts b/packages/react-resizable-panels/src/new/utils/validatePanelGroupLayout.ts index 93f0a8658..58bdc9b05 100644 --- a/packages/react-resizable-panels/src/new/utils/validatePanelGroupLayout.ts +++ b/packages/react-resizable-panels/src/new/utils/validatePanelGroupLayout.ts @@ -1,4 +1,4 @@ -import { fuzzyNumbersEqual } from "./fuzzyNumbersEqual"; +import { fuzzyNumbersEqual } from "./numbers/fuzzyNumbersEqual"; import { resizePanel } from "./resizePanel"; import { PanelConstraints } from "../Panel"; From bc8f2490764b554766a4950f8c077fc8d2478fac Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 21 Oct 2023 08:10:46 -0400 Subject: [PATCH 08/35] Partially migrate imperative examples to new Panel and PanelGroup --- .../routes/examples/ExternalPersistence.tsx | 22 ++- .../routes/examples/ImperativePanelApi.tsx | 30 +-- .../examples/ImperativePanelGroupApi.tsx | 25 +-- .../src/routes/examples/PixelBasedLayouts.tsx | 21 +- packages/react-resizable-panels/src/index.ts | 4 + .../src/new/PanelGroup.ts | 51 ++++- .../src/new/utils/adjustLayoutByDelta.test.ts | 182 +++++++++++++++++- .../src/new/utils/adjustLayoutByDelta.ts | 16 +- 8 files changed, 286 insertions(+), 65 deletions(-) diff --git a/packages/react-resizable-panels-website/src/routes/examples/ExternalPersistence.tsx b/packages/react-resizable-panels-website/src/routes/examples/ExternalPersistence.tsx index b1b6b0b8d..1d7a900fe 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/ExternalPersistence.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/ExternalPersistence.tsx @@ -1,8 +1,12 @@ import { useMemo } from "react"; -import { Panel, PanelGroup, PanelGroupStorage } from "react-resizable-panels"; +import { + new_Panel as Panel, + new_PanelGroup as PanelGroup, + PanelGroupStorage, +} from "react-resizable-panels"; import { useNavigate } from "react-router-dom"; -import ResizeHandle from "../../components/ResizeHandle"; +import { new_ResizeHandle as ResizeHandle } from "../../components/ResizeHandle"; import Example from "./Example"; import styles from "./shared.module.css"; @@ -81,15 +85,23 @@ function Content() { direction="horizontal" storage={urlStorage} > - +
left
- +
middle
- +
right
diff --git a/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx b/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx index 30143d6a2..355a040f4 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx @@ -1,12 +1,12 @@ import { ChangeEvent, FormEvent, RefObject, useRef, useState } from "react"; import { - ImperativePanelHandle, - Panel, - PanelGroup, + new_ImperativePanelHandle as ImperativePanelHandle, + new_Panel as Panel, + new_PanelGroup as PanelGroup, } from "react-resizable-panels"; import Icon from "../../components/Icon"; -import ResizeHandle from "../../components/ResizeHandle"; +import { new_ResizeHandle as ResizeHandle } from "../../components/ResizeHandle"; import Example from "./Example"; import styles from "./ImperativePanelApi.module.css"; @@ -212,11 +212,11 @@ function Content({ onResize({ left })} + maxSizePercentage={30} + minSizePercentage={10} + onResize={({ sizePercentage: left }) => onResize({ left })} order={1} ref={leftPanelRef} > @@ -229,9 +229,9 @@ function Content({ className={sharedStyles.PanelRow} collapsible={true} id="middle" - maxSize={100} - minSize={10} - onResize={(middle: number) => onResize({ middle })} + maxSizePercentage={100} + minSizePercentage={10} + onResize={({ sizePercentage: middle }) => onResize({ middle })} order={2} ref={middlePanelRef} > @@ -243,11 +243,11 @@ function Content({ onResize({ right })} + maxSizePercentage={100} + minSizePercentage={10} + onResize={({ sizePercentage: right }) => onResize({ right })} order={3} ref={rightPanelRef} > diff --git a/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelGroupApi.tsx b/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelGroupApi.tsx index 89088d9f7..df7ac93e8 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelGroupApi.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelGroupApi.tsx @@ -1,23 +1,16 @@ -import { RefObject, useRef, useState } from "react"; +import { useRef, useState } from "react"; +import type { new_ImperativePanelGroupHandle as ImperativePanelGroupHandle } from "react-resizable-panels"; import { - ImperativePanelHandle, - Panel, - PanelGroup, + new_Panel as Panel, + new_PanelGroup as PanelGroup, } from "react-resizable-panels"; -import ResizeHandle from "../../components/ResizeHandle"; +import { new_ResizeHandle as ResizeHandle } from "../../components/ResizeHandle"; +import Code from "../../components/Code"; import Example from "./Example"; import styles from "./ImperativePanelGroupApi.module.css"; import sharedStyles from "./shared.module.css"; -import Code from "../../components/Code"; -import { ImperativePanelGroupHandle } from "react-resizable-panels"; - -type Sizes = { - left: number; - middle: number; - right: number; -}; export default function ImperativePanelGroupApiRoute() { return ( @@ -68,7 +61,7 @@ function Content() { const resetLayout = () => { const panelGroup = panelGroupRef.current; if (panelGroup) { - panelGroup.setLayout([50, 50]); + panelGroup.setLayout([{ sizePercentage: 50 }, { sizePercentage: 50 }]); } }; @@ -87,13 +80,13 @@ function Content() { onLayout={onLayout} ref={panelGroupRef} > - +
left: {Math.round(sizes[0])}
- +
right: {Math.round(sizes[1])}
diff --git a/packages/react-resizable-panels-website/src/routes/examples/PixelBasedLayouts.tsx b/packages/react-resizable-panels-website/src/routes/examples/PixelBasedLayouts.tsx index 3a4a08c3d..ec7d8e243 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/PixelBasedLayouts.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/PixelBasedLayouts.tsx @@ -22,15 +22,16 @@ export default function PixelBasedLayouts() { →Pixel based layouts

- Resizable panels typically use percentage-based layout constraints, but - pixel units are also supported via the units prop. The - example below shows a horizontal panel group where the first panel is - limited to a range of 100-200 pixels. + Resizable panels can specific either percentage-based{" "} + or pixel-based layout constraints, although + percentage-based constraints are generally recommended for performance + purposes. The example below shows a horizontal panel group where the + first panel is limited to a range of 100-200 pixels.

- Pixel units should only be used when necessary because they require more - complex layout logic. + Pixel units should only be used when necessary because they require the + use of a ResizerObserver.

@@ -139,8 +140,8 @@ function Size({ } const CODE_HOOK = ` - - + + @@ -149,11 +150,11 @@ const CODE_HOOK = ` `; const CODE_HOOK_COLLAPSIBLE = ` - + - + `; diff --git a/packages/react-resizable-panels/src/index.ts b/packages/react-resizable-panels/src/index.ts index 74385ddf5..dd53da68b 100644 --- a/packages/react-resizable-panels/src/index.ts +++ b/packages/react-resizable-panels/src/index.ts @@ -4,7 +4,9 @@ import { PanelResizeHandle } from "./PanelResizeHandle"; // TEMP import { Panel as new_Panel } from "./new/Panel"; +import type { ImperativePanelHandle as new_ImperativePanelHandle } from "./new/Panel"; import { PanelGroup as new_PanelGroup } from "./new/PanelGroup"; +import type { ImperativePanelGroupHandle as new_ImperativePanelGroupHandle } from "./new/PanelGroup"; import { PanelResizeHandle as new_PanelResizeHandle } from "./new/PanelResizeHandle"; import type { ImperativePanelHandle, PanelProps } from "./Panel"; @@ -43,6 +45,8 @@ export { getAvailableGroupSizePixels, // TEMP + new_ImperativePanelGroupHandle, + new_ImperativePanelHandle, new_Panel, new_PanelGroup, new_PanelResizeHandle, diff --git a/packages/react-resizable-panels/src/new/PanelGroup.ts b/packages/react-resizable-panels/src/new/PanelGroup.ts index 3e510a029..9305802ba 100644 --- a/packages/react-resizable-panels/src/new/PanelGroup.ts +++ b/packages/react-resizable-panels/src/new/PanelGroup.ts @@ -6,17 +6,20 @@ import debounce from "../utils/debounce"; import { CSSProperties, ElementType, + ForwardedRef, PropsWithChildren, createElement, + forwardRef, useCallback, useEffect, + useImperativeHandle, useMemo, useRef, useState, } from "../vendor/react"; import { PanelData } from "./Panel"; import { DragState, PanelGroupContext, ResizeEvent } from "./PanelGroupContext"; -import { Direction } from "./types"; +import { Direction, MixedSizes } from "./types"; import { adjustLayoutByDelta } from "./utils/adjustLayoutByDelta"; import { calculateDefaultLayout } from "./utils/calculateDefaultLayout"; import { calculateDeltaPercentage } from "./utils/calculateDeltaPercentage"; @@ -27,7 +30,7 @@ import { convertPercentageToPixels } from "./utils/convertPercentageToPixels"; import { determinePivotIndices } from "./utils/determinePivotIndices"; import { calculateAvailablePanelSizeInPixels } from "./utils/dom/calculateAvailablePanelSizeInPixels"; import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement"; -import { isMouseEvent, isTouchEvent } from "./utils/events"; +import { isKeyDown, isMouseEvent, isTouchEvent } from "./utils/events"; import { getResizeEventCursorPosition } from "./utils/getResizeEventCursorPosition"; import { initializeDefaultStorage } from "./utils/initializeDefaultStorage"; import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization"; @@ -37,6 +40,12 @@ import { validatePanelGroupLayout } from "./utils/validatePanelGroupLayout"; // TODO Use ResizeObserver (but only if any Panels declare pixels units) +export type ImperativePanelGroupHandle = { + getId: () => string; + getLayout: () => MixedSizes[]; + setLayout: (layout: Partial[]) => void; +}; + export type PanelGroupStorage = { getItem(name: string): string | null; setItem(name: string, value: string): void; @@ -73,17 +82,20 @@ const debounceMap: { [key: string]: typeof savePanelGroupLayout; } = {}; -export function PanelGroup({ +function PanelGroupWithForwardedRef({ autoSaveId, children, className: classNameFromProps = "", direction, + forwardedRef, id: idFromProps, onLayout, storage = defaultStorage, style: styleFromProps, tagName: Type = "div", -}: PanelGroupProps) { +}: PanelGroupProps & { + forwardedRef: ForwardedRef; +}) { const groupId = useUniqueId(idFromProps); const [dragState, setDragState] = useState(null); @@ -106,6 +118,21 @@ export function PanelGroup({ panelDataArray, }); + useImperativeHandle( + forwardedRef, + () => ({ + getId: () => groupId, + getLayout: () => { + // TODO + return []; + }, + setLayout: (layout: Partial[]) => { + // TODO + }, + }), + [groupId] + ); + useIsomorphicLayoutEffect(() => { committedValuesRef.current.direction = direction; committedValuesRef.current.dragState = dragState; @@ -196,12 +223,14 @@ export function PanelGroup({ layout, panelConstraints: panelConstraintsArray, pivotIndices, + trigger: "imperative-api", }); if (!compareLayouts(layout, nextLayout)) { setLayout(nextLayout); // TODO Callbacks and stuff (put in helper function that takes prevLayout, nextLayout) + // onLayout() } } } @@ -236,6 +265,7 @@ export function PanelGroup({ layout, panelConstraints: panelConstraintsArray, pivotIndices, + trigger: "imperative-api", }); if (!compareLayouts(layout, nextLayout)) { @@ -370,12 +400,12 @@ export function PanelGroup({ ); const nextLayout = adjustLayoutByDelta({ - collapsedPanelMode: "snap", delta, groupSizePixels, layout: initialLayout ?? prevLayout, panelConstraints, pivotIndices, + trigger: isKeyDown(event) ? "keyboard" : "mouse-or-touch", }); const layoutChanged = !compareLayouts(prevLayout, nextLayout); @@ -434,6 +464,7 @@ export function PanelGroup({ layout, panelConstraints: panelConstraintsArray, pivotIndices, + trigger: "imperative-api", }); if (!compareLayouts(layout, nextLayout)) { @@ -549,6 +580,16 @@ export function PanelGroup({ ); } +export const PanelGroup = forwardRef< + ImperativePanelGroupHandle, + PanelGroupProps +>((props: PanelGroupProps, ref: ForwardedRef) => + createElement(PanelGroupWithForwardedRef, { ...props, forwardedRef: ref }) +); + +PanelGroupWithForwardedRef.displayName = "PanelGroup"; +PanelGroup.displayName = "forwardRef(PanelGroup)"; + function panelDataHelper( groupId: string, panelDataArray: PanelData[], diff --git a/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.test.ts b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.test.ts index 8a2ff6642..cb85eef33 100644 --- a/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.test.ts +++ b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.test.ts @@ -9,6 +9,7 @@ describe("adjustLayoutByDelta", () => { layout: [50, 50], panelConstraints: [{}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([51, 49]); }); @@ -21,6 +22,7 @@ describe("adjustLayoutByDelta", () => { layout: [50, 50], panelConstraints: [{}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([75, 25]); expect( @@ -30,6 +32,7 @@ describe("adjustLayoutByDelta", () => { layout: [50, 50], panelConstraints: [{}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([100, 0]); }); @@ -51,6 +54,7 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([60, 40]); }); @@ -70,6 +74,7 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([75, 25]); }); @@ -89,10 +94,35 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([95, 5]); }); + // Edge case + // Expanding from a collapsed state to less than the min size via imperative API should do nothing + it("[1++,2]", () => { + expect( + adjustLayoutByDelta({ + delta: 5, + groupSizePixels: NaN, + layout: [10, 90], + panelConstraints: [ + { + collapsedSizePercentage: 10, + collapsible: true, + minSizePercentage: 25, + }, + {}, + ], + pivotIndices: [0, 1], + trigger: "imperative-api", + }) + ).toEqual([10, 90]); + }); + + // Edge case + // Expanding from a collapsed state to less than the min size via keyboard should snap to min size it("[1++,2]", () => { expect( adjustLayoutByDelta({ @@ -108,12 +138,13 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [0, 1], + trigger: "keyboard", }) ).toEqual([25, 75]); }); // Edge case - // Expanding from a collapsed state to less than the min size + // Expanding from a collapsed state to less than the min size via imperative API should do nothing it("[1++,2]", () => { // collapsed 4%, min size 6%, max size 15% expect( @@ -134,6 +165,34 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [0, 1], + trigger: "imperative-api", + }) + ).toEqual([4, 96]); + }); + + // Edge case + // Expanding from a collapsed state to less than the min size via keyboard should snap to min size + it("[1++,2]", () => { + // collapsed 4%, min size 6%, max size 15% + expect( + adjustLayoutByDelta({ + delta: 1, + groupSizePixels: 1_000, + layout: [4, 96], + panelConstraints: [ + { + collapsedSizePixels: 40, + collapsible: true, + defaultSizePixels: 150, + maxSizePixels: 150, + minSizePixels: 60, + }, + { + minSizePercentage: 50, + }, + ], + pivotIndices: [0, 1], + trigger: "keyboard", }) ).toEqual([6, 94]); }); @@ -160,16 +219,16 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([15, 85]); }); // Edge case - // Expanding from a collapsed state to a valid size in "permissive" (default) mode + // Expanding from a collapsed state mimicking an imperative API call it("[1++,2]", () => { expect( adjustLayoutByDelta({ - collapsedPanelMode: "permissive", delta: 30, groupSizePixels: 100, layout: [5, 95], @@ -185,16 +244,16 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([35, 65]); }); // Edge case - // Expanding from a collapsed state to a valid size in "snap" mode + // Expanding from a collapsed state mimicking an keyboard event it("[1++,2]", () => { expect( adjustLayoutByDelta({ - collapsedPanelMode: "snap", delta: 30, groupSizePixels: 100, layout: [5, 95], @@ -210,8 +269,32 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [0, 1], + trigger: "keyboard", }) - ).toEqual([25, 75]); + ).toEqual([35, 65]); + }); + + // Edge case + // Expanding from a collapsed state mimicking an keyboard event when there is no min size + it("[1++,2]", () => { + expect( + adjustLayoutByDelta({ + delta: 30, + groupSizePixels: 100, + layout: [0, 100], + panelConstraints: [ + { + collapsedSizePixels: 0, + collapsible: true, + maxSizePixels: 50, + minSizePixels: 0, + }, + {}, + ], + pivotIndices: [0, 1], + trigger: "keyboard", + }) + ).toEqual([30, 70]); }); it("[1--,2]", () => { @@ -222,6 +305,7 @@ describe("adjustLayoutByDelta", () => { layout: [50, 50], panelConstraints: [{}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([49, 51]); }); @@ -234,6 +318,7 @@ describe("adjustLayoutByDelta", () => { layout: [50, 50], panelConstraints: [{}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([25, 75]); }); @@ -246,6 +331,7 @@ describe("adjustLayoutByDelta", () => { layout: [50, 50], panelConstraints: [{}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([0, 100]); }); @@ -267,6 +353,7 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([20, 80]); }); @@ -286,6 +373,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([25, 75]); }); @@ -305,6 +393,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([5, 95]); }); @@ -326,6 +415,7 @@ describe("adjustLayoutByDelta", () => { { maxSizePercentage: 80 }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([25, 75]); }); @@ -338,6 +428,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([26, 49, 25]); }); @@ -350,6 +441,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([50, 25, 25]); }); @@ -362,6 +454,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([75, 0, 25]); }); @@ -374,6 +467,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([100, 0, 0]); }); @@ -390,6 +484,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([35, 40, 25]); }); @@ -407,6 +502,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([35, 40, 25]); }); @@ -427,6 +523,7 @@ describe("adjustLayoutByDelta", () => { { minSizePercentage: 25 }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([30, 35, 35]); }); @@ -447,6 +544,7 @@ describe("adjustLayoutByDelta", () => { { minSizePercentage: 25 }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([60, 5, 35]); }); @@ -467,6 +565,7 @@ describe("adjustLayoutByDelta", () => { { minSizePercentage: 25 }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([70, 5, 25]); }); @@ -479,6 +578,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([24, 51, 25]); }); @@ -491,6 +591,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([0, 75, 25]); }); @@ -503,6 +604,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{ minSizePercentage: 20 }, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([24, 51, 25]); }); @@ -515,6 +617,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{ minSizePercentage: 20 }, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([20, 55, 25]); }); @@ -533,6 +636,7 @@ describe("adjustLayoutByDelta", () => { { maxSizePercentage: 20 }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([20, 55, 25]); }); @@ -551,6 +655,7 @@ describe("adjustLayoutByDelta", () => { { maxSizePercentage: 20 }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([10, 65, 25]); }); @@ -571,6 +676,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([15, 60, 25]); }); @@ -591,6 +697,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([5, 70, 25]); }); @@ -613,6 +720,7 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([45, 50, 5]); }); @@ -625,6 +733,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 49, 26]); }); @@ -637,6 +746,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 25, 50]); }); @@ -649,6 +759,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 0, 75]); }); @@ -661,6 +772,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([0, 0, 100]); }); @@ -673,6 +785,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, { minSizePercentage: 15 }], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 55, 20]); }); @@ -685,6 +798,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, { minSizePercentage: 15 }], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 60, 15]); }); @@ -701,6 +815,7 @@ describe("adjustLayoutByDelta", () => { { collapsible: true, minSizePercentage: 20 }, ], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 55, 20]); }); @@ -717,6 +832,7 @@ describe("adjustLayoutByDelta", () => { { collapsible: true, minSizePercentage: 20 }, ], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 75, 0]); }); @@ -729,6 +845,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 51, 24]); }); @@ -741,6 +858,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 75, 0]); }); @@ -753,6 +871,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, { minSizePercentage: 40 }, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([15, 40, 45]); }); @@ -765,6 +884,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 50, 25], panelConstraints: [{}, {}, { maxSizePercentage: 30 }], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 45, 30]); }); @@ -785,6 +905,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 5, 70]); }); @@ -805,6 +926,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([5, 0, 95]); }); @@ -817,6 +939,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([26, 24, 25, 25]); }); @@ -829,6 +952,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([50, 0, 25, 25]); }); @@ -841,6 +965,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([75, 0, 0, 25]); }); @@ -853,6 +978,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([100, 0, 0, 0]); }); @@ -865,6 +991,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{ maxSizePercentage: 35 }, {}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([35, 15, 25, 25]); }); @@ -882,6 +1009,7 @@ describe("adjustLayoutByDelta", () => { { minSizePercentage: 10 }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([70, 10, 10, 10]); }); @@ -911,6 +1039,7 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([45, 5, 25, 25]); }); @@ -940,6 +1069,7 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([65, 5, 5, 25]); }); @@ -969,6 +1099,7 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([85, 5, 5, 5]); }); @@ -981,6 +1112,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([24, 26, 25, 25]); }); @@ -993,6 +1125,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([0, 50, 25, 25]); }); @@ -1005,6 +1138,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{ minSizePercentage: 20 }, {}, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([20, 30, 25, 25]); }); @@ -1017,6 +1151,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, { maxSizePercentage: 35 }, {}, {}], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([15, 35, 25, 25]); }); @@ -1038,6 +1173,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([5, 45, 25, 25]); }); @@ -1059,6 +1195,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([5, 35, 35, 25]); }); @@ -1086,6 +1223,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([20, 30, 25, 25]); }); @@ -1113,6 +1251,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([20, 30, 25, 25]); }); @@ -1134,6 +1273,7 @@ describe("adjustLayoutByDelta", () => { { maxSizePercentage: 35 }, ], pivotIndices: [0, 1], + trigger: "imperative-api", }) ).toEqual([5, 35, 35, 25]); }); @@ -1146,6 +1286,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 35, 15, 25]); }); @@ -1158,6 +1299,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 55, 0, 20]); }); @@ -1170,6 +1312,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 75, 0, 0]); }); @@ -1182,6 +1325,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, { maxSizePercentage: 35 }, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 35, 15, 25]); }); @@ -1194,6 +1338,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, { minSizePercentage: 20 }, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 55, 20, 0]); }); @@ -1215,6 +1360,7 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 35, 15, 25]); }); @@ -1236,6 +1382,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 55, 5, 15]); }); @@ -1257,6 +1404,7 @@ describe("adjustLayoutByDelta", () => { { minSizePercentage: 10 }, ], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 60, 5, 10]); }); @@ -1269,6 +1417,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 0, 50, 25]); }); @@ -1281,6 +1430,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([0, 0, 75, 25]); }); @@ -1293,6 +1443,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, { minSizePercentage: 20 }, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([0, 20, 55, 25]); }); @@ -1305,6 +1456,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{ minSizePercentage: 20 }, {}, {}, {}], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([20, 0, 55, 25]); }); @@ -1322,6 +1474,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([20, 20, 35, 25]); }); @@ -1343,6 +1496,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([25, 20, 30, 25]); }); @@ -1364,6 +1518,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([5, 0, 70, 25]); }); @@ -1385,6 +1540,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [1, 2], + trigger: "imperative-api", }) ).toEqual([0, 5, 70, 25]); }); @@ -1397,6 +1553,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([25, 25, 35, 15]); }); @@ -1409,6 +1566,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([25, 25, 50, 0]); }); @@ -1421,6 +1579,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, { maxSizePercentage: 40 }, {}], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([25, 25, 40, 10]); }); @@ -1433,6 +1592,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, { minSizePercentage: 10 }], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([25, 25, 40, 10]); }); @@ -1454,6 +1614,7 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([25, 25, 30, 20]); }); @@ -1475,6 +1636,7 @@ describe("adjustLayoutByDelta", () => { }, ], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([25, 25, 45, 5]); }); @@ -1487,6 +1649,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([25, 25, 15, 35]); }); @@ -1499,6 +1662,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([25, 10, 0, 65]); }); @@ -1511,6 +1675,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, {}], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([0, 0, 0, 100]); }); @@ -1528,6 +1693,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([10, 10, 10, 70]); }); @@ -1540,6 +1706,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, {}, {}, { maxSizePercentage: 40 }], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([25, 25, 10, 40]); }); @@ -1552,6 +1719,7 @@ describe("adjustLayoutByDelta", () => { layout: [25, 25, 25, 25], panelConstraints: [{}, { minSizePercentage: 5 }, {}, {}], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([20, 5, 0, 75]); }); @@ -1581,6 +1749,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([5, 5, 5, 85]); }); @@ -1606,6 +1775,7 @@ describe("adjustLayoutByDelta", () => { {}, ], pivotIndices: [2, 3], + trigger: "imperative-api", }) ).toEqual([20, 5, 20, 55]); }); diff --git a/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts index 8caf1eb86..4e56dc57f 100644 --- a/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts +++ b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts @@ -8,19 +8,19 @@ let isCheckingForInfiniteLoop = false; // All units must be in percentages; pixel values should be pre-converted export function adjustLayoutByDelta({ - collapsedPanelMode = "permissive", delta, groupSizePixels, layout: prevLayout, panelConstraints, pivotIndices, + trigger, }: { - collapsedPanelMode?: "permissive" | "snap"; delta: number; groupSizePixels: number; layout: number[]; panelConstraints: PanelConstraints[]; pivotIndices: number[]; + trigger: "imperative-api" | "keyboard" | "mouse-or-touch"; }): number[] { if (fuzzyNumbersEqual(delta, 0)) { return prevLayout; @@ -57,12 +57,11 @@ export function adjustLayoutByDelta({ let unsafeSize = initialSize + Math.abs(delta); if (isCollapsed) { - if (fuzzyCompareNumbers(unsafeSize, maxSizePercentage) > 0) { - unsafeSize = maxSizePercentage; - } else if (fuzzyCompareNumbers(unsafeSize, minSizePercentage) < 0) { - unsafeSize = minSizePercentage; - } else if (collapsedPanelMode === "snap") { - unsafeSize = minSizePercentage; + switch (trigger) { + case "keyboard": + if (minSizePercentage > unsafeSize) { + unsafeSize = minSizePercentage; + } } } @@ -192,6 +191,7 @@ export function adjustLayoutByDelta({ layout: prevLayout, panelConstraints, pivotIndices, + trigger, }); } catch (error) { if (error instanceof RangeError) { From 9806d6bb9d5a1f5a754812cb368c60fe55ffcd8f Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 21 Oct 2023 09:10:15 -0400 Subject: [PATCH 09/35] Implement PanelGroup setLayout API --- .../src/new/PanelGroup.ts | 61 ++++++++++++++++--- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/packages/react-resizable-panels/src/new/PanelGroup.ts b/packages/react-resizable-panels/src/new/PanelGroup.ts index 9305802ba..c5a98f770 100644 --- a/packages/react-resizable-panels/src/new/PanelGroup.ts +++ b/packages/react-resizable-panels/src/new/PanelGroup.ts @@ -1,6 +1,7 @@ import { isDevelopment } from "#is-development"; import useIsomorphicLayoutEffect from "../hooks/useIsomorphicEffect"; import useUniqueId from "../hooks/useUniqueId"; +import { areEqual } from "../utils/arrays"; import { resetGlobalCursorStyle, setGlobalCursorStyle } from "../utils/cursor"; import debounce from "../utils/debounce"; import { @@ -27,6 +28,7 @@ import { compareLayouts } from "./utils/compareLayouts"; import { computePanelFlexBoxStyle } from "./utils/computePanelFlexBoxStyle"; import { computePercentagePanelConstraints } from "./utils/computePercentagePanelConstraints"; import { convertPercentageToPixels } from "./utils/convertPercentageToPixels"; +import { convertPixelsToPercentage } from "./utils/convertPixelsToPercentage"; import { determinePivotIndices } from "./utils/determinePivotIndices"; import { calculateAvailablePanelSizeInPixels } from "./utils/dom/calculateAvailablePanelSizeInPixels"; import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement"; @@ -121,16 +123,59 @@ function PanelGroupWithForwardedRef({ useImperativeHandle( forwardedRef, () => ({ - getId: () => groupId, + getId: () => committedValuesRef.current.id, getLayout: () => { - // TODO - return []; + const { id: groupId, layout } = committedValuesRef.current; + + const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId); + + return layout.map((sizePercentage) => { + return { + sizePercentage, + sizePixels: convertPercentageToPixels( + sizePercentage, + groupSizePixels + ), + }; + }); }, - setLayout: (layout: Partial[]) => { - // TODO + setLayout: (mixedSizes: Partial[]) => { + const { + id: groupId, + layout: prevLayout, + panelDataArray, + } = committedValuesRef.current; + + const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId); + + const unsafeLayout = mixedSizes.map( + ({ sizePercentage, sizePixels }) => { + if (sizePercentage != null) { + return sizePercentage; + } else if (sizePixels != null) { + return convertPixelsToPercentage(sizePixels, groupSizePixels); + } else { + throw Error("Invalid layout"); + } + } + ); + + const safeLayout = validatePanelGroupLayout({ + groupSizePixels, + layout: unsafeLayout, + panelConstraints: panelDataArray.map( + (panelData) => panelData.constraints + ), + }); + + if (!areEqual(prevLayout, safeLayout)) { + setLayout(safeLayout); + + // TODO Callbacks + } }, }), - [groupId] + [] ); useIsomorphicLayoutEffect(() => { @@ -194,7 +239,9 @@ function PanelGroupWithForwardedRef({ ), }); - setLayout(validatedLayout); + if (!areEqual(layout, validatedLayout)) { + setLayout(validatedLayout); + } }, [autoSaveId, layout, panelDataArray, storage]); // External APIs are safe to memoize via committed values ref From 30ed78cf252c5d76c0016846ed9717c4eb4afc29 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 21 Oct 2023 09:51:18 -0400 Subject: [PATCH 10/35] Wired up onLayout and onResize/onExpand/onCollapse callbacks --- .../examples/ImperativePanelGroupApi.tsx | 9 +- packages/react-resizable-panels/src/index.ts | 2 + .../react-resizable-panels/src/new/Panel.ts | 11 +- .../src/new/PanelGroup.ts | 178 ++++++++++++++---- .../src/new/utils/callPanelCallbacks.ts | 77 ++++++++ .../getPercentageSizeFromMixedSizes.test.ts | 49 +++++ .../utils/getPercentageSizeFromMixedSizes.ts | 15 ++ 7 files changed, 297 insertions(+), 44 deletions(-) create mode 100644 packages/react-resizable-panels/src/new/utils/callPanelCallbacks.ts create mode 100644 packages/react-resizable-panels/src/new/utils/getPercentageSizeFromMixedSizes.test.ts create mode 100644 packages/react-resizable-panels/src/new/utils/getPercentageSizeFromMixedSizes.ts diff --git a/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelGroupApi.tsx b/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelGroupApi.tsx index df7ac93e8..b1274c969 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelGroupApi.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelGroupApi.tsx @@ -1,5 +1,8 @@ import { useRef, useState } from "react"; -import type { new_ImperativePanelGroupHandle as ImperativePanelGroupHandle } from "react-resizable-panels"; +import type { + MixedSizes, + new_ImperativePanelGroupHandle as ImperativePanelGroupHandle, +} from "react-resizable-panels"; import { new_Panel as Panel, new_PanelGroup as PanelGroup, @@ -54,8 +57,8 @@ function Content() { const panelGroupRef = useRef(null); - const onLayout = (sizes: number[]) => { - setSizes(sizes); + const onLayout = (mixedSizes: MixedSizes[]) => { + setSizes(mixedSizes.map((mixedSize) => mixedSize.sizePercentage)); }; const resetLayout = () => { diff --git a/packages/react-resizable-panels/src/index.ts b/packages/react-resizable-panels/src/index.ts index dd53da68b..ae4763058 100644 --- a/packages/react-resizable-panels/src/index.ts +++ b/packages/react-resizable-panels/src/index.ts @@ -3,6 +3,7 @@ import { PanelGroup } from "./PanelGroup"; import { PanelResizeHandle } from "./PanelResizeHandle"; // TEMP +import type { MixedSizes } from "./new/types"; import { Panel as new_Panel } from "./new/Panel"; import type { ImperativePanelHandle as new_ImperativePanelHandle } from "./new/Panel"; import { PanelGroup as new_PanelGroup } from "./new/PanelGroup"; @@ -26,6 +27,7 @@ export { // TypeScript types ImperativePanelGroupHandle, ImperativePanelHandle, + MixedSizes, PanelOnCollapse, PanelOnResize, PanelGroupOnLayout, diff --git a/packages/react-resizable-panels/src/new/Panel.ts b/packages/react-resizable-panels/src/new/Panel.ts index 8a0248958..4bfb7d74b 100644 --- a/packages/react-resizable-panels/src/new/Panel.ts +++ b/packages/react-resizable-panels/src/new/Panel.ts @@ -16,13 +16,10 @@ import { MixedSizes } from "./types"; export type OnCollapse = () => void; export type OnExpand = () => void; -export type OnResize = ({ - sizePercentage, - sizePixels, -}: { - sizePercentage: number; - sizePixels: number; -}) => void; +export type OnResize = ( + mixedSizes: MixedSizes, + prevMixedSizes: MixedSizes +) => void; export type PanelCallbacks = { onCollapse?: OnCollapse; diff --git a/packages/react-resizable-panels/src/new/PanelGroup.ts b/packages/react-resizable-panels/src/new/PanelGroup.ts index c5a98f770..93d07ec55 100644 --- a/packages/react-resizable-panels/src/new/PanelGroup.ts +++ b/packages/react-resizable-panels/src/new/PanelGroup.ts @@ -24,15 +24,16 @@ import { Direction, MixedSizes } from "./types"; import { adjustLayoutByDelta } from "./utils/adjustLayoutByDelta"; import { calculateDefaultLayout } from "./utils/calculateDefaultLayout"; import { calculateDeltaPercentage } from "./utils/calculateDeltaPercentage"; +import { callPanelCallbacks } from "./utils/callPanelCallbacks"; import { compareLayouts } from "./utils/compareLayouts"; import { computePanelFlexBoxStyle } from "./utils/computePanelFlexBoxStyle"; import { computePercentagePanelConstraints } from "./utils/computePercentagePanelConstraints"; import { convertPercentageToPixels } from "./utils/convertPercentageToPixels"; -import { convertPixelsToPercentage } from "./utils/convertPixelsToPercentage"; import { determinePivotIndices } from "./utils/determinePivotIndices"; import { calculateAvailablePanelSizeInPixels } from "./utils/dom/calculateAvailablePanelSizeInPixels"; import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement"; import { isKeyDown, isMouseEvent, isTouchEvent } from "./utils/events"; +import { getPercentageSizeFromMixedSizes } from "./utils/getPercentageSizeFromMixedSizes"; import { getResizeEventCursorPosition } from "./utils/getResizeEventCursorPosition"; import { initializeDefaultStorage } from "./utils/initializeDefaultStorage"; import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization"; @@ -41,6 +42,7 @@ import { validatePanelGroupLayout } from "./utils/validatePanelGroupLayout"; // TODO Move group/DOM helpers into new package // TODO Use ResizeObserver (but only if any Panels declare pixels units) +// ResizeObserver should trigger validatePanelGroupLayout() and callPanelCallbacks() when size changes export type ImperativePanelGroupHandle = { getId: () => string; @@ -53,7 +55,7 @@ export type PanelGroupStorage = { setItem(name: string, value: string): void; }; -export type PanelGroupOnLayout = (sizes: number[]) => void; +export type PanelGroupOnLayout = (layout: MixedSizes[]) => void; export type PanelOnCollapse = (collapsed: boolean) => void; export type PanelOnResize = (size: number, prevSize: number) => void; export type PanelResizeHandleOnDragging = (isDragging: boolean) => void; @@ -74,7 +76,7 @@ export type PanelGroupProps = PropsWithChildren<{ className?: string; direction: Direction; id?: string | null; - onLayout?: PanelGroupOnLayout; + onLayout?: PanelGroupOnLayout | null; storage?: PanelGroupStorage; style?: CSSProperties; tagName?: ElementType; @@ -91,7 +93,7 @@ function PanelGroupWithForwardedRef({ direction, forwardedRef, id: idFromProps, - onLayout, + onLayout = null, storage = defaultStorage, style: styleFromProps, tagName: Type = "div", @@ -104,6 +106,9 @@ function PanelGroupWithForwardedRef({ const [layout, setLayout] = useState([]); const [panelDataArray, setPanelDataArray] = useState([]); + const panelIdToLastNotifiedMixedSizesMapRef = useRef< + Record + >({}); const prevDeltaRef = useRef(0); const committedValuesRef = useRef<{ @@ -111,12 +116,14 @@ function PanelGroupWithForwardedRef({ dragState: DragState | null; id: string; layout: number[]; + onLayout: PanelGroupOnLayout | null; panelDataArray: PanelData[]; }>({ direction, dragState, id: groupId, layout, + onLayout, panelDataArray, }); @@ -143,21 +150,14 @@ function PanelGroupWithForwardedRef({ const { id: groupId, layout: prevLayout, + onLayout, panelDataArray, } = committedValuesRef.current; const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId); - const unsafeLayout = mixedSizes.map( - ({ sizePercentage, sizePixels }) => { - if (sizePercentage != null) { - return sizePercentage; - } else if (sizePixels != null) { - return convertPixelsToPercentage(sizePixels, groupSizePixels); - } else { - throw Error("Invalid layout"); - } - } + const unsafeLayout = mixedSizes.map((mixedSize) => + getPercentageSizeFromMixedSizes(mixedSize, groupSizePixels) ); const safeLayout = validatePanelGroupLayout({ @@ -171,7 +171,24 @@ function PanelGroupWithForwardedRef({ if (!areEqual(prevLayout, safeLayout)) { setLayout(safeLayout); - // TODO Callbacks + if (onLayout) { + onLayout( + safeLayout.map((sizePercentage) => ({ + sizePercentage, + sizePixels: convertPercentageToPixels( + sizePercentage, + groupSizePixels + ), + })) + ); + } + + callPanelCallbacks( + groupId, + panelDataArray, + safeLayout, + panelIdToLastNotifiedMixedSizesMapRef.current + ); } }, }), @@ -183,6 +200,7 @@ function PanelGroupWithForwardedRef({ committedValuesRef.current.dragState = dragState; committedValuesRef.current.id = groupId; committedValuesRef.current.layout = layout; + committedValuesRef.current.onLayout = onLayout; committedValuesRef.current.panelDataArray = panelDataArray; }); @@ -207,7 +225,7 @@ function PanelGroupWithForwardedRef({ // Compute the initial sizes based on default weights. // This assumes that panels register during initial mount (no conditional rendering)! useIsomorphicLayoutEffect(() => { - const { id: groupId, layout } = committedValuesRef.current; + const { id: groupId, layout, onLayout } = committedValuesRef.current; if (layout.length === panelDataArray.length) { // Only compute (or restore) default layout once per panel configuration. return; @@ -242,12 +260,28 @@ function PanelGroupWithForwardedRef({ if (!areEqual(layout, validatedLayout)) { setLayout(validatedLayout); } + + if (onLayout) { + onLayout( + validatedLayout.map((sizePercentage) => ({ + sizePercentage, + sizePixels: convertPercentageToPixels( + sizePercentage, + groupSizePixels + ), + })) + ); + } }, [autoSaveId, layout, panelDataArray, storage]); // External APIs are safe to memoize via committed values ref const collapsePanel = useCallback( (panelData: PanelData) => { - const { layout, panelDataArray } = committedValuesRef.current; + const { + layout: prevLayout, + onLayout, + panelDataArray, + } = committedValuesRef.current; if (panelData.constraints.collapsible) { const panelConstraintsArray = panelDataArray.map( @@ -259,7 +293,7 @@ function PanelGroupWithForwardedRef({ panelSizePercentage, pivotIndices, groupSizePixels, - } = panelDataHelper(groupId, panelDataArray, panelData, layout); + } = panelDataHelper(groupId, panelDataArray, panelData, prevLayout); if (panelSizePercentage !== collapsedSizePercentage) { // TODO Store size before collapse @@ -267,17 +301,33 @@ function PanelGroupWithForwardedRef({ const nextLayout = adjustLayoutByDelta({ delta: collapsedSizePercentage - panelSizePercentage, groupSizePixels, - layout, + layout: prevLayout, panelConstraints: panelConstraintsArray, pivotIndices, trigger: "imperative-api", }); - if (!compareLayouts(layout, nextLayout)) { + if (!compareLayouts(prevLayout, nextLayout)) { setLayout(nextLayout); - // TODO Callbacks and stuff (put in helper function that takes prevLayout, nextLayout) - // onLayout() + if (onLayout) { + onLayout( + nextLayout.map((sizePercentage) => ({ + sizePercentage, + sizePixels: convertPercentageToPixels( + sizePercentage, + groupSizePixels + ), + })) + ); + } + + callPanelCallbacks( + groupId, + panelDataArray, + nextLayout, + panelIdToLastNotifiedMixedSizesMapRef.current + ); } } } @@ -288,7 +338,11 @@ function PanelGroupWithForwardedRef({ // External APIs are safe to memoize via committed values ref const expandPanel = useCallback( (panelData: PanelData) => { - const { layout, panelDataArray } = committedValuesRef.current; + const { + layout: prevLayout, + onLayout, + panelDataArray, + } = committedValuesRef.current; if (panelData.constraints.collapsible) { const panelConstraintsArray = panelDataArray.map( @@ -301,24 +355,41 @@ function PanelGroupWithForwardedRef({ minSizePercentage, pivotIndices, groupSizePixels, - } = panelDataHelper(groupId, panelDataArray, panelData, layout); + } = panelDataHelper(groupId, panelDataArray, panelData, prevLayout); if (panelSizePercentage === collapsedSizePercentage) { - // TODO Retrieve size before collapse + // TODO Retrieve size before collapse; this should be the default new size const nextLayout = adjustLayoutByDelta({ delta: minSizePercentage - panelSizePercentage, groupSizePixels, - layout, + layout: prevLayout, panelConstraints: panelConstraintsArray, pivotIndices, trigger: "imperative-api", }); - if (!compareLayouts(layout, nextLayout)) { + if (!compareLayouts(prevLayout, nextLayout)) { setLayout(nextLayout); - // TODO Callbacks and stuff (put in helper function that takes prevLayout, nextLayout) + if (onLayout) { + onLayout( + nextLayout.map((sizePercentage) => ({ + sizePercentage, + sizePixels: convertPercentageToPixels( + sizePercentage, + groupSizePixels + ), + })) + ); + } + + callPanelCallbacks( + groupId, + panelDataArray, + nextLayout, + panelIdToLastNotifiedMixedSizesMapRef.current + ); } } } @@ -416,6 +487,7 @@ function PanelGroupWithForwardedRef({ direction, dragState, id: groupId, + onLayout, panelDataArray, layout: prevLayout, } = committedValuesRef.current; @@ -488,7 +560,24 @@ function PanelGroupWithForwardedRef({ if (layoutChanged) { setLayout(nextLayout); - // TODO Callbacks and stuff (put in helper function that takes prevLayout, nextLayout) + if (onLayout) { + onLayout( + nextLayout.map((sizePercentage) => ({ + sizePercentage, + sizePixels: convertPercentageToPixels( + sizePercentage, + groupSizePixels + ), + })) + ); + } + + callPanelCallbacks( + groupId, + panelDataArray, + nextLayout, + panelIdToLastNotifiedMixedSizesMapRef.current + ); } }; }, []); @@ -496,28 +585,49 @@ function PanelGroupWithForwardedRef({ // External APIs are safe to memoize via committed values ref const resizePanel = useCallback( (panelData: PanelData, sizePercentage: number) => { - const { layout, panelDataArray } = committedValuesRef.current; + const { + layout: prevLayout, + onLayout, + panelDataArray, + } = committedValuesRef.current; const panelConstraintsArray = panelDataArray.map( (panelData) => panelData.constraints ); const { groupSizePixels, panelSizePercentage, pivotIndices } = - panelDataHelper(groupId, panelDataArray, panelData, layout); + panelDataHelper(groupId, panelDataArray, panelData, prevLayout); const nextLayout = adjustLayoutByDelta({ delta: sizePercentage - panelSizePercentage, groupSizePixels, - layout, + layout: prevLayout, panelConstraints: panelConstraintsArray, pivotIndices, trigger: "imperative-api", }); - if (!compareLayouts(layout, nextLayout)) { + if (!compareLayouts(prevLayout, nextLayout)) { setLayout(nextLayout); - // TODO Callbacks and stuff (put in helper function that takes prevLayout, nextLayout) + if (onLayout) { + onLayout( + nextLayout.map((sizePercentage) => ({ + sizePercentage, + sizePixels: convertPercentageToPixels( + sizePercentage, + groupSizePixels + ), + })) + ); + } + + callPanelCallbacks( + groupId, + panelDataArray, + nextLayout, + panelIdToLastNotifiedMixedSizesMapRef.current + ); } }, [groupId] diff --git a/packages/react-resizable-panels/src/new/utils/callPanelCallbacks.ts b/packages/react-resizable-panels/src/new/utils/callPanelCallbacks.ts new file mode 100644 index 000000000..04e0ad306 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/callPanelCallbacks.ts @@ -0,0 +1,77 @@ +import { PanelData } from "../Panel"; +import { MixedSizes } from "../types"; +import { convertPercentageToPixels } from "./convertPercentageToPixels"; +import { calculateAvailablePanelSizeInPixels } from "./dom/calculateAvailablePanelSizeInPixels"; +import { getPercentageSizeFromMixedSizes } from "./getPercentageSizeFromMixedSizes"; + +// Layout should be pre-converted into percentages +export function callPanelCallbacks( + groupId: string, + panelsArray: PanelData[], + layout: number[], + panelIdToLastNotifiedMixedSizesMap: Record +) { + const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId); + + layout.forEach((sizePercentage, index) => { + const panelData = panelsArray[index]; + if (!panelData) { + // Handle initial mount (when panels are registered too late to be in the panels array) + // The subsequent render+effects will handle the resize notification + return; + } + + const { callbacks, constraints, id: panelId } = panelData; + const { collapsible } = constraints; + + const mixedSizes: MixedSizes = { + sizePercentage, + sizePixels: convertPercentageToPixels(sizePercentage, groupSizePixels), + }; + + const lastNotifiedMixedSizes = panelIdToLastNotifiedMixedSizesMap[panelId]; + if ( + lastNotifiedMixedSizes == null || + mixedSizes.sizePercentage !== lastNotifiedMixedSizes.sizePercentage || + mixedSizes.sizePixels !== lastNotifiedMixedSizes.sizePixels + ) { + panelIdToLastNotifiedMixedSizesMap[panelId] = mixedSizes; + + const { onCollapse, onExpand, onResize } = callbacks; + + if (onResize) { + onResize(mixedSizes, lastNotifiedMixedSizes); + } + + if (collapsible && (onCollapse || onExpand)) { + const collapsedSize = getPercentageSizeFromMixedSizes( + { + sizePercentage: constraints.collapsedSizePercentage, + sizePixels: constraints.collapsedSizePixels, + }, + groupSizePixels + ); + + const size = getPercentageSizeFromMixedSizes( + mixedSizes, + groupSizePixels + ); + + if ( + onExpand && + (lastNotifiedMixedSizes == null || + lastNotifiedMixedSizes.sizePercentage === collapsedSize) && + size !== collapsedSize + ) { + onExpand(); + } else if ( + onCollapse && + lastNotifiedMixedSizes.sizePercentage !== collapsedSize && + size === collapsedSize + ) { + onCollapse(); + } + } + } + }); +} diff --git a/packages/react-resizable-panels/src/new/utils/getPercentageSizeFromMixedSizes.test.ts b/packages/react-resizable-panels/src/new/utils/getPercentageSizeFromMixedSizes.test.ts new file mode 100644 index 000000000..23b4892e1 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/getPercentageSizeFromMixedSizes.test.ts @@ -0,0 +1,49 @@ +import { getPercentageSizeFromMixedSizes } from "./getPercentageSizeFromMixedSizes"; + +describe("getPercentageSizeFromMixedSizes", () => { + it("should return percentage sizes as-is", () => { + expect( + getPercentageSizeFromMixedSizes( + { + sizePercentage: 50, + }, + 100_000 + ) + ).toBe(50); + expect( + getPercentageSizeFromMixedSizes( + { + sizePercentage: 25, + sizePixels: 100, + }, + 100_000 + ) + ).toBe(25); + }); + + it("should convert pixels to percentages", () => { + expect( + getPercentageSizeFromMixedSizes( + { + sizePixels: 50_000, + }, + 100_000 + ) + ).toBe(50); + expect( + getPercentageSizeFromMixedSizes( + { + sizePercentage: 25, + sizePixels: 50_000, + }, + 100_000 + ) + ).toBe(25); + }); + + it("should throw if neither pixel nor percentage sizes specified", () => { + expect(() => getPercentageSizeFromMixedSizes({}, 100_000)).toThrowError( + "Invalid size" + ); + }); +}); diff --git a/packages/react-resizable-panels/src/new/utils/getPercentageSizeFromMixedSizes.ts b/packages/react-resizable-panels/src/new/utils/getPercentageSizeFromMixedSizes.ts new file mode 100644 index 000000000..3fc9ed910 --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/getPercentageSizeFromMixedSizes.ts @@ -0,0 +1,15 @@ +import { MixedSizes } from "../types"; +import { convertPixelsToPercentage } from "./convertPixelsToPercentage"; + +export function getPercentageSizeFromMixedSizes( + { sizePercentage, sizePixels }: Partial, + groupSizePixels: number +): number { + if (sizePercentage != null) { + return sizePercentage; + } else if (sizePixels != null) { + return convertPixelsToPercentage(sizePixels, groupSizePixels); + } + + throw Error("Invalid size"); +} From a9edc8f0b148d4dab1155b3cf152d47eb2a4ec9b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 21 Oct 2023 10:38:39 -0400 Subject: [PATCH 11/35] Added unit tests for calculateUnsafeDefaultLayout --- .../react-resizable-panels/jest.config.js | 1 + .../src/new/PanelGroup.ts | 4 +- .../src/new/utils/adjustLayoutByDelta.ts | 1 - .../calculateUnsafeDefaultLayout.test.ts | 92 +++++++++++++++++++ ...out.ts => calculateUnsafeDefaultLayout.ts} | 4 +- .../src/new/utils/test-utils.ts | 16 ++++ 6 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 packages/react-resizable-panels/src/new/utils/calculateUnsafeDefaultLayout.test.ts rename packages/react-resizable-panels/src/new/utils/{calculateDefaultLayout.ts => calculateUnsafeDefaultLayout.ts} (94%) diff --git a/packages/react-resizable-panels/jest.config.js b/packages/react-resizable-panels/jest.config.js index ea6df48e4..792238f11 100644 --- a/packages/react-resizable-panels/jest.config.js +++ b/packages/react-resizable-panels/jest.config.js @@ -1,6 +1,7 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { preset: "ts-jest", + prettierPath: null, testEnvironmentOptions: { customExportConditions: ["development"], }, diff --git a/packages/react-resizable-panels/src/new/PanelGroup.ts b/packages/react-resizable-panels/src/new/PanelGroup.ts index 93d07ec55..94e8639a9 100644 --- a/packages/react-resizable-panels/src/new/PanelGroup.ts +++ b/packages/react-resizable-panels/src/new/PanelGroup.ts @@ -22,7 +22,7 @@ import { PanelData } from "./Panel"; import { DragState, PanelGroupContext, ResizeEvent } from "./PanelGroupContext"; import { Direction, MixedSizes } from "./types"; import { adjustLayoutByDelta } from "./utils/adjustLayoutByDelta"; -import { calculateDefaultLayout } from "./utils/calculateDefaultLayout"; +import { calculateUnsafeDefaultLayout } from "./utils/calculateUnsafeDefaultLayout"; import { calculateDeltaPercentage } from "./utils/calculateDeltaPercentage"; import { callPanelCallbacks } from "./utils/callPanelCallbacks"; import { compareLayouts } from "./utils/compareLayouts"; @@ -241,7 +241,7 @@ function PanelGroupWithForwardedRef({ const groupSizePixels = calculateAvailablePanelSizeInPixels(groupId); if (unsafeLayout == null) { - unsafeLayout = calculateDefaultLayout({ + unsafeLayout = calculateUnsafeDefaultLayout({ groupSizePixels, panelDataArray, }); diff --git a/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts index 4e56dc57f..9bf0ca6a2 100644 --- a/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts +++ b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.ts @@ -2,7 +2,6 @@ import { computePercentagePanelConstraints } from "./computePercentagePanelConst import { fuzzyNumbersEqual } from "./numbers/fuzzyNumbersEqual"; import { resizePanel } from "./resizePanel"; import { PanelConstraints } from "../Panel"; -import { fuzzyCompareNumbers } from "./numbers/fuzzyCompareNumbers"; let isCheckingForInfiniteLoop = false; diff --git a/packages/react-resizable-panels/src/new/utils/calculateUnsafeDefaultLayout.test.ts b/packages/react-resizable-panels/src/new/utils/calculateUnsafeDefaultLayout.test.ts new file mode 100644 index 000000000..b34bcb43e --- /dev/null +++ b/packages/react-resizable-panels/src/new/utils/calculateUnsafeDefaultLayout.test.ts @@ -0,0 +1,92 @@ +import { PanelConstraints, PanelData } from "../Panel"; +import { calculateUnsafeDefaultLayout } from "./calculateUnsafeDefaultLayout"; +import { expectToBeCloseToArray } from "./test-utils"; + +describe("calculateUnsafeDefaultLayout", () => { + let idCounter = 0; + let orderCounter = 0; + + function createPanelData(constraints: PanelConstraints = {}): PanelData { + return { + callbacks: { + onCollapse: undefined, + onExpand: undefined, + onResize: undefined, + }, + constraints, + id: `${idCounter++}`, + idIsFromProps: false, + order: orderCounter++, + }; + } + + beforeEach(() => { + idCounter = 0; + orderCounter = 0; + }); + + it("should assign even sizes for every panel by default", () => { + expectToBeCloseToArray( + calculateUnsafeDefaultLayout({ + groupSizePixels: 100_000, + panelDataArray: [createPanelData()], + }), + [100] + ); + + expectToBeCloseToArray( + calculateUnsafeDefaultLayout({ + groupSizePixels: 100_000, + panelDataArray: [createPanelData(), createPanelData()], + }), + [50, 50] + ); + + expectToBeCloseToArray( + calculateUnsafeDefaultLayout({ + groupSizePixels: 100_000, + panelDataArray: [ + createPanelData(), + createPanelData(), + createPanelData(), + ], + }), + [33.3, 33.3, 33.3] + ); + }); + + it("should respect default panel size constraints", () => { + expectToBeCloseToArray( + calculateUnsafeDefaultLayout({ + groupSizePixels: 100_000, + panelDataArray: [ + createPanelData({ + defaultSizePercentage: 15, + }), + createPanelData({ + defaultSizePercentage: 85, + }), + ], + }), + [15, 85] + ); + }); + + it("should ignore min and max panel size constraints", () => { + expectToBeCloseToArray( + calculateUnsafeDefaultLayout({ + groupSizePixels: 100_000, + panelDataArray: [ + createPanelData({ + minSizePercentage: 40, + }), + createPanelData(), + createPanelData({ + maxSizePercentage: 10, + }), + ], + }), + [33.3, 33.3, 33.3] + ); + }); +}); diff --git a/packages/react-resizable-panels/src/new/utils/calculateDefaultLayout.ts b/packages/react-resizable-panels/src/new/utils/calculateUnsafeDefaultLayout.ts similarity index 94% rename from packages/react-resizable-panels/src/new/utils/calculateDefaultLayout.ts rename to packages/react-resizable-panels/src/new/utils/calculateUnsafeDefaultLayout.ts index 615facffa..f7caea026 100644 --- a/packages/react-resizable-panels/src/new/utils/calculateDefaultLayout.ts +++ b/packages/react-resizable-panels/src/new/utils/calculateUnsafeDefaultLayout.ts @@ -1,9 +1,7 @@ import { PanelData } from "../Panel"; import { computePercentagePanelConstraints } from "./computePercentagePanelConstraints"; -// TODO Add basic unit tests for this - -export function calculateDefaultLayout({ +export function calculateUnsafeDefaultLayout({ groupSizePixels, panelDataArray, }: { diff --git a/packages/react-resizable-panels/src/new/utils/test-utils.ts b/packages/react-resizable-panels/src/new/utils/test-utils.ts index fc15d45a2..a54bfe913 100644 --- a/packages/react-resizable-panels/src/new/utils/test-utils.ts +++ b/packages/react-resizable-panels/src/new/utils/test-utils.ts @@ -1,5 +1,21 @@ const util = require("util"); +export function expectToBeCloseToArray( + actualNumbers: number[], + expectedNumbers: number[] +) { + expect(actualNumbers.length).toBe(expectedNumbers.length); + + try { + actualNumbers.forEach((actualNumber, index) => { + const expectedNumber = expectedNumbers[index]; + expect(actualNumber).toBeCloseTo(expectedNumber, 1); + }); + } catch (error) { + expect(actualNumbers).toEqual(expectedNumbers); + } +} + export function verifyExpectedWarnings( callback: Function, ...expectedMessages: string[] From e1a97bf9a098b70d6efab636d51044e9220198ac Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 21 Oct 2023 17:24:09 -0400 Subject: [PATCH 12/35] Added tests for PanelGroup DEV warnings and adjustLayoutByDelta util function --- .../react-resizable-panels/jest.config.js | 1 + .../react-resizable-panels/src/PanelGroup.ts | 2 - .../src/new/PanelGroup.test.tsx | 93 +++++++++++++++++++ .../src/new/PanelGroup.ts | 68 +++++++++++++- .../src/new/utils/adjustLayoutByDelta.test.ts | 4 - 5 files changed, 158 insertions(+), 10 deletions(-) create mode 100644 packages/react-resizable-panels/src/new/PanelGroup.test.tsx diff --git a/packages/react-resizable-panels/jest.config.js b/packages/react-resizable-panels/jest.config.js index 792238f11..8180d7906 100644 --- a/packages/react-resizable-panels/jest.config.js +++ b/packages/react-resizable-panels/jest.config.js @@ -1,5 +1,6 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { + testEnvironment: "jsdom", preset: "ts-jest", prettierPath: null, testEnvironmentOptions: { diff --git a/packages/react-resizable-panels/src/PanelGroup.ts b/packages/react-resizable-panels/src/PanelGroup.ts index 272c05ef6..145862f91 100644 --- a/packages/react-resizable-panels/src/PanelGroup.ts +++ b/packages/react-resizable-panels/src/PanelGroup.ts @@ -169,12 +169,10 @@ function PanelGroupWithForwardedRef({ const devWarningsRef = useRef<{ didLogDefaultSizeWarning: boolean; didLogIdAndOrderWarning: boolean; - didLogInvalidLayoutWarning: boolean; prevPanelIds: string[]; }>({ didLogDefaultSizeWarning: false, didLogIdAndOrderWarning: false, - didLogInvalidLayoutWarning: false, prevPanelIds: [], }); diff --git a/packages/react-resizable-panels/src/new/PanelGroup.test.tsx b/packages/react-resizable-panels/src/new/PanelGroup.test.tsx new file mode 100644 index 000000000..ad92c8adc --- /dev/null +++ b/packages/react-resizable-panels/src/new/PanelGroup.test.tsx @@ -0,0 +1,93 @@ +import { Root, createRoot } from "react-dom/client"; +import { act } from "react-dom/test-utils"; +import { Panel } from "./Panel"; +import { PanelGroup } from "./PanelGroup"; + +describe("PanelGroup", () => { + let expectedWarnings: string[] = []; + let root: Root; + + function expectWarning(expectedMessage: string) { + expectedWarnings.push(expectedMessage); + } + + beforeEach(() => { + // @ts-expect-error + global.IS_REACT_ACT_ENVIRONMENT = true; + + expectedWarnings = []; + root = createRoot(document.createElement("div")); + + jest.spyOn(console, "warn").mockImplementation((actualMessage: string) => { + const match = expectedWarnings.findIndex((expectedMessage) => { + return actualMessage.includes(expectedMessage); + }); + + if (match >= 0) { + expectedWarnings.splice(match, 1); + return; + } + + throw Error(`Unexpected warning: ${actualMessage}`); + }); + }); + + afterEach(() => { + expect(expectedWarnings).toHaveLength(0); + + jest.clearAllMocks(); + jest.resetModules(); + + act(() => { + root.unmount(); + }); + }); + + describe("DEV warnings", () => { + it("should warn about unstable layouts without id and order props", () => { + act(() => { + root.render( + + + + ); + }); + + expectWarning( + "Panel id and order props recommended when panels are dynamically rendered" + ); + + act(() => { + root.render( + + + + + ); + }); + }); + + it("should warn about server rendered panels with no default size", () => { + jest.resetModules(); + jest.mock("#is-browser", () => ({ isBrowser: false })); + + const { createRoot } = require("react-dom/client"); + const { act } = require("react-dom/test-utils"); + const Panel = require("./Panel").Panel; + const PanelGroup = require("./PanelGroup").PanelGroup; + + expectWarning( + "Panel defaultSizePercentage or defaultSizePixels prop recommended to avoid layout shift after server rendering" + ); + + act(() => { + const root = createRoot(document.createElement("div")); + root.render( + + + + ); + }); + }); + }); +}); diff --git a/packages/react-resizable-panels/src/new/PanelGroup.ts b/packages/react-resizable-panels/src/new/PanelGroup.ts index 94e8639a9..60c6eb2f4 100644 --- a/packages/react-resizable-panels/src/new/PanelGroup.ts +++ b/packages/react-resizable-panels/src/new/PanelGroup.ts @@ -1,3 +1,4 @@ +import { isBrowser } from "#is-browser"; import { isDevelopment } from "#is-development"; import useIsomorphicLayoutEffect from "../hooks/useIsomorphicEffect"; import useUniqueId from "../hooks/useUniqueId"; @@ -39,8 +40,6 @@ import { initializeDefaultStorage } from "./utils/initializeDefaultStorage"; import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization"; import { validatePanelGroupLayout } from "./utils/validatePanelGroupLayout"; -// TODO Move group/DOM helpers into new package - // TODO Use ResizeObserver (but only if any Panels declare pixels units) // ResizeObserver should trigger validatePanelGroupLayout() and callPanelCallbacks() when size changes @@ -127,6 +126,18 @@ function PanelGroupWithForwardedRef({ panelDataArray, }); + const devWarningsRef = useRef<{ + didLogDefaultSizeWarning: boolean; + didLogIdAndOrderWarning: boolean; + didLogInvalidLayoutWarning: boolean; + prevPanelIds: string[]; + }>({ + didLogDefaultSizeWarning: false, + didLogIdAndOrderWarning: false, + didLogInvalidLayoutWarning: false, + prevPanelIds: [], + }); + useImperativeHandle( forwardedRef, () => ({ @@ -204,8 +215,6 @@ function PanelGroupWithForwardedRef({ committedValuesRef.current.panelDataArray = panelDataArray; }); - // TODO Dev warnings - useEffect(() => { // If this panel has been configured to persist sizing information, save sizes to local storage. if (autoSaveId) { @@ -274,6 +283,37 @@ function PanelGroupWithForwardedRef({ } }, [autoSaveId, layout, panelDataArray, storage]); + // DEV warnings + useEffect(() => { + if (isDevelopment) { + const { didLogIdAndOrderWarning, prevPanelIds } = devWarningsRef.current; + + if (!didLogIdAndOrderWarning) { + const { panelDataArray } = committedValuesRef.current; + + const panelIds = panelDataArray.map(({ id }) => id); + + devWarningsRef.current.prevPanelIds = panelIds; + + const panelsHaveChanged = + prevPanelIds.length > 0 && !areEqual(prevPanelIds, panelIds); + if (panelsHaveChanged) { + if ( + panelDataArray.find( + ({ idIsFromProps, order }) => !idIsFromProps || order == null + ) + ) { + devWarningsRef.current.didLogIdAndOrderWarning = true; + + console.warn( + `WARNING: Panel id and order props recommended when panels are dynamically rendered` + ); + } + } + } + } + }); + // External APIs are safe to memoize via committed values ref const collapsePanel = useCallback( (panelData: PanelData) => { @@ -422,6 +462,26 @@ function PanelGroupWithForwardedRef({ (panelData: PanelData) => { const panelIndex = panelDataArray.indexOf(panelData); + // Before mounting, Panels will not yet have registered themselves. + // This includes server rendering. + // At this point the best we can do is render everything with the same size. + if (panelDataArray.length === 0) { + if (isDevelopment) { + if (!devWarningsRef.current.didLogDefaultSizeWarning) { + if ( + !isBrowser && + panelData.constraints.defaultSizePercentage == null && + panelData.constraints.defaultSizePixels == null + ) { + devWarningsRef.current.didLogDefaultSizeWarning = true; + console.warn( + `WARNING: Panel defaultSizePercentage or defaultSizePixels prop recommended to avoid layout shift after server rendering` + ); + } + } + } + } + return computePanelFlexBoxStyle({ dragState, layout, diff --git a/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.test.ts b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.test.ts index cb85eef33..b9a9faef7 100644 --- a/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.test.ts +++ b/packages/react-resizable-panels/src/new/utils/adjustLayoutByDelta.test.ts @@ -1780,9 +1780,5 @@ describe("adjustLayoutByDelta", () => { ).toEqual([20, 5, 20, 55]); }); - // TODO expandToSize after collapsed - - // TODO Infinite loop check - // TODO Invalid pixel constraints (min too large, max too small) should recover by recomputing base? }); From 5c544bb96c9221fe47c449781bd8a12a954a2f66 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 21 Oct 2023 20:09:15 -0400 Subject: [PATCH 13/35] Add more tests for DEV warnings and imperative APIs --- .../src/hooks/useUniqueId.ts | 2 +- .../src/new/Panel.test.tsx | 151 ++++++++++++++++++ .../react-resizable-panels/src/new/Panel.ts | 35 +++- .../src/new/PanelGroup.test.tsx | 105 +++++++++--- .../src/new/PanelGroup.ts | 58 ++++--- .../src/new/PanelGroupContext.ts | 2 +- .../src/new/utils/test-utils.ts | 43 +++++ .../src/vendor/react.ts | 2 + 8 files changed, 337 insertions(+), 61 deletions(-) create mode 100644 packages/react-resizable-panels/src/new/Panel.test.tsx diff --git a/packages/react-resizable-panels/src/hooks/useUniqueId.ts b/packages/react-resizable-panels/src/hooks/useUniqueId.ts index e668d1626..a3d49a814 100644 --- a/packages/react-resizable-panels/src/hooks/useUniqueId.ts +++ b/packages/react-resizable-panels/src/hooks/useUniqueId.ts @@ -15,5 +15,5 @@ export default function useUniqueId( idRef.current = "" + counter++; } - return idRef.current; + return idFromParams ?? idRef.current; } diff --git a/packages/react-resizable-panels/src/new/Panel.test.tsx b/packages/react-resizable-panels/src/new/Panel.test.tsx new file mode 100644 index 000000000..5f8fc3f1c --- /dev/null +++ b/packages/react-resizable-panels/src/new/Panel.test.tsx @@ -0,0 +1,151 @@ +import { Root, createRoot } from "react-dom/client"; +import { act } from "react-dom/test-utils"; +import { createRef } from "../vendor/react"; +import { ImperativePanelHandle, Panel } from "./Panel"; +import { PanelGroup } from "./PanelGroup"; +import { mockOffsetWidthAndHeight } from "./utils/test-utils"; +import { MixedSizes } from ".."; + +describe("PanelGroup", () => { + let expectedWarnings: string[] = []; + let root: Root; + let uninstallMockOffsetWidthAndHeight: () => void; + + function expectWarning(expectedMessage: string) { + expectedWarnings.push(expectedMessage); + } + + beforeEach(() => { + // @ts-expect-error + global.IS_REACT_ACT_ENVIRONMENT = true; + + uninstallMockOffsetWidthAndHeight = mockOffsetWidthAndHeight(); + + const container = document.createElement("div"); + document.body.appendChild(container); + + expectedWarnings = []; + root = createRoot(container); + + jest.spyOn(console, "warn").mockImplementation((actualMessage: string) => { + const match = expectedWarnings.findIndex((expectedMessage) => { + return actualMessage.includes(expectedMessage); + }); + + if (match >= 0) { + expectedWarnings.splice(match, 1); + return; + } + + throw Error(`Unexpected warning: ${actualMessage}`); + }); + }); + + afterEach(() => { + uninstallMockOffsetWidthAndHeight(); + + jest.clearAllMocks(); + jest.resetModules(); + + act(() => { + root.unmount(); + }); + + expect(expectedWarnings).toHaveLength(0); + }); + + describe("imperative handle API", () => { + it("should re-expand to the most recent size before collapsing", () => { + const ref = createRef(); + + let mostRecentMixedSizes: MixedSizes | undefined; + + const onResize = (mixedSizes: MixedSizes) => { + mostRecentMixedSizes = mixedSizes; + }; + + act(() => { + root.render( + + + + + ); + }); + + expect(mostRecentMixedSizes).toEqual({ + sizePercentage: 50, + sizePixels: 500, + }); + + act(() => { + ref.current!.resize({ sizePercentage: 30 }); + }); + + expect(mostRecentMixedSizes).toEqual({ + sizePercentage: 30, + sizePixels: 300, + }); + + act(() => { + ref.current!.collapse(); + }); + + expect(mostRecentMixedSizes).toEqual({ + sizePercentage: 5, + sizePixels: 50, + }); + + act(() => { + ref.current!.expand(); + }); + + expect(mostRecentMixedSizes).toEqual({ + sizePercentage: 30, + sizePixels: 300, + }); + }); + }); + + describe("DEV warnings", () => { + it("should warn about server rendered panels with no default size", () => { + jest.resetModules(); + jest.mock("#is-browser", () => ({ isBrowser: false })); + + const { createRoot } = require("react-dom/client"); + const { act } = require("react-dom/test-utils"); + const Panel = require("./Panel").Panel; + const PanelGroup = require("./PanelGroup").PanelGroup; + + act(() => { + const root = createRoot(document.createElement("div")); + root.render( + + + + + ); + }); + + expectWarning( + "Panel defaultSizePercentage or defaultSizePixels prop recommended to avoid layout shift after server rendering" + ); + + act(() => { + const root = createRoot(document.createElement("div")); + root.render( + + + + ); + }); + }); + }); +}); diff --git a/packages/react-resizable-panels/src/new/Panel.ts b/packages/react-resizable-panels/src/new/Panel.ts index 4bfb7d74b..3691b7768 100644 --- a/packages/react-resizable-panels/src/new/Panel.ts +++ b/packages/react-resizable-panels/src/new/Panel.ts @@ -1,4 +1,5 @@ -import { isDevelopment } from "../env-conditions/production"; +import { isBrowser } from "#is-browser"; +import { isDevelopment } from "#is-development"; import useIsomorphicLayoutEffect from "../hooks/useIsomorphicEffect"; import useUniqueId from "../hooks/useUniqueId"; import { @@ -13,6 +14,8 @@ import { } from "../vendor/react"; import { PanelGroupContext } from "./PanelGroupContext"; import { MixedSizes } from "./types"; +import { calculateAvailablePanelSizeInPixels } from "./utils/dom/calculateAvailablePanelSizeInPixels"; +import { getPercentageSizeFromMixedSizes } from "./utils/getPercentageSizeFromMixedSizes"; export type OnCollapse = () => void; export type OnExpand = () => void; @@ -52,7 +55,7 @@ export type ImperativePanelHandle = { expand: () => void; getId(): string; getSize(): MixedSizes; - resize: (percentage: number) => void; + resize: (size: Partial) => void; }; export type PanelProps = PropsWithChildren<{ @@ -140,6 +143,29 @@ export function PanelWithForwardedRef({ order, }); + const devWarningsRef = useRef<{ + didLogDefaultSizeWarning: boolean; + }>({ + didLogDefaultSizeWarning: false, + }); + + // Normally we wouldn't log a warning during render, + // but effects don't run on the server, so we can't do it there + if (isDevelopment) { + if (!devWarningsRef.current.didLogDefaultSizeWarning) { + if ( + !isBrowser && + defaultSizePercentage == null && + defaultSizePixels == null + ) { + devWarningsRef.current.didLogDefaultSizeWarning = true; + console.warn( + `WARNING: Panel defaultSizePercentage or defaultSizePixels prop recommended to avoid layout shift after server rendering` + ); + } + } + } + useIsomorphicLayoutEffect(() => { const { callbacks, constraints } = panelDataRef.current; @@ -193,8 +219,9 @@ export function PanelWithForwardedRef({ isExpanded() { return !isPanelCollapsed(panelDataRef.current); }, - resize: (percentage: number) => - resizePanel(panelDataRef.current, percentage), + resize: (mixedSizes: Partial) => { + resizePanel(panelDataRef.current, mixedSizes); + }, }), [ collapsePanel, diff --git a/packages/react-resizable-panels/src/new/PanelGroup.test.tsx b/packages/react-resizable-panels/src/new/PanelGroup.test.tsx index ad92c8adc..852c71464 100644 --- a/packages/react-resizable-panels/src/new/PanelGroup.test.tsx +++ b/packages/react-resizable-panels/src/new/PanelGroup.test.tsx @@ -1,11 +1,15 @@ import { Root, createRoot } from "react-dom/client"; import { act } from "react-dom/test-utils"; +import { createRef } from "../vendor/react"; import { Panel } from "./Panel"; -import { PanelGroup } from "./PanelGroup"; +import { ImperativePanelGroupHandle, PanelGroup } from "./PanelGroup"; +import { MixedSizes } from ".."; +import { mockOffsetWidthAndHeight } from "./utils/test-utils"; describe("PanelGroup", () => { let expectedWarnings: string[] = []; let root: Root; + let uninstallMockOffsetWidthAndHeight: () => void; function expectWarning(expectedMessage: string) { expectedWarnings.push(expectedMessage); @@ -15,8 +19,14 @@ describe("PanelGroup", () => { // @ts-expect-error global.IS_REACT_ACT_ENVIRONMENT = true; + // JSDom doesn't support element sizes + uninstallMockOffsetWidthAndHeight = mockOffsetWidthAndHeight(); + + const container = document.createElement("div"); + document.body.appendChild(container); + expectedWarnings = []; - root = createRoot(document.createElement("div")); + root = createRoot(container); jest.spyOn(console, "warn").mockImplementation((actualMessage: string) => { const match = expectedWarnings.findIndex((expectedMessage) => { @@ -33,7 +43,7 @@ describe("PanelGroup", () => { }); afterEach(() => { - expect(expectedWarnings).toHaveLength(0); + uninstallMockOffsetWidthAndHeight(); jest.clearAllMocks(); jest.resetModules(); @@ -41,50 +51,95 @@ describe("PanelGroup", () => { act(() => { root.unmount(); }); + + expect(expectedWarnings).toHaveLength(0); }); - describe("DEV warnings", () => { - it("should warn about unstable layouts without id and order props", () => { + describe("imperative handle API", () => { + it("should report the most recently rendered group id", () => { + const ref = createRef(); + act(() => { - root.render( - - - - ); + root.render(); }); - expectWarning( - "Panel id and order props recommended when panels are dynamically rendered" - ); + expect(ref.current!.getId()).toBe("one"); + + act(() => { + root.render(); + }); + + expect(ref.current!.getId()).toBe("two"); + }); + + it("should get and set layouts", () => { + const ref = createRef(); + + let mostRecentLayout: MixedSizes[] | null = null; + + const onLayout = (layout: MixedSizes[]) => { + mostRecentLayout = layout; + }; act(() => { root.render( - + ); }); - }); - it("should warn about server rendered panels with no default size", () => { - jest.resetModules(); - jest.mock("#is-browser", () => ({ isBrowser: false })); + expect(mostRecentLayout).toEqual([ + { + sizePercentage: 50, + sizePixels: 500, + }, + { + sizePercentage: 50, + sizePixels: 500, + }, + ]); - const { createRoot } = require("react-dom/client"); - const { act } = require("react-dom/test-utils"); - const Panel = require("./Panel").Panel; - const PanelGroup = require("./PanelGroup").PanelGroup; + act(() => { + ref.current!.setLayout([ + { sizePercentage: 25 }, + { sizePercentage: 75 }, + ]); + }); + + expect(mostRecentLayout).toEqual([ + { + sizePercentage: 25, + sizePixels: 250, + }, + { + sizePercentage: 75, + sizePixels: 750, + }, + ]); + }); + }); + + describe("DEV warnings", () => { + it("should warn about unstable layouts without id and order props", () => { + act(() => { + root.render( + + + + ); + }); expectWarning( - "Panel defaultSizePercentage or defaultSizePixels prop recommended to avoid layout shift after server rendering" + "Panel id and order props recommended when panels are dynamically rendered" ); act(() => { - const root = createRoot(document.createElement("div")); root.render( - + + ); }); diff --git a/packages/react-resizable-panels/src/new/PanelGroup.ts b/packages/react-resizable-panels/src/new/PanelGroup.ts index 60c6eb2f4..83b35943e 100644 --- a/packages/react-resizable-panels/src/new/PanelGroup.ts +++ b/packages/react-resizable-panels/src/new/PanelGroup.ts @@ -1,4 +1,3 @@ -import { isBrowser } from "#is-browser"; import { isDevelopment } from "#is-development"; import useIsomorphicLayoutEffect from "../hooks/useIsomorphicEffect"; import useUniqueId from "../hooks/useUniqueId"; @@ -23,8 +22,8 @@ import { PanelData } from "./Panel"; import { DragState, PanelGroupContext, ResizeEvent } from "./PanelGroupContext"; import { Direction, MixedSizes } from "./types"; import { adjustLayoutByDelta } from "./utils/adjustLayoutByDelta"; -import { calculateUnsafeDefaultLayout } from "./utils/calculateUnsafeDefaultLayout"; import { calculateDeltaPercentage } from "./utils/calculateDeltaPercentage"; +import { calculateUnsafeDefaultLayout } from "./utils/calculateUnsafeDefaultLayout"; import { callPanelCallbacks } from "./utils/callPanelCallbacks"; import { compareLayouts } from "./utils/compareLayouts"; import { computePanelFlexBoxStyle } from "./utils/computePanelFlexBoxStyle"; @@ -108,6 +107,7 @@ function PanelGroupWithForwardedRef({ const panelIdToLastNotifiedMixedSizesMapRef = useRef< Record >({}); + const panelSizeBeforeCollapseRef = useRef>(new Map()); const prevDeltaRef = useRef(0); const committedValuesRef = useRef<{ @@ -127,14 +127,10 @@ function PanelGroupWithForwardedRef({ }); const devWarningsRef = useRef<{ - didLogDefaultSizeWarning: boolean; didLogIdAndOrderWarning: boolean; - didLogInvalidLayoutWarning: boolean; prevPanelIds: string[]; }>({ - didLogDefaultSizeWarning: false, didLogIdAndOrderWarning: false, - didLogInvalidLayoutWarning: false, prevPanelIds: [], }); @@ -281,6 +277,13 @@ function PanelGroupWithForwardedRef({ })) ); } + + callPanelCallbacks( + groupId, + panelDataArray, + validatedLayout, + panelIdToLastNotifiedMixedSizesMapRef.current + ); }, [autoSaveId, layout, panelDataArray, storage]); // DEV warnings @@ -336,7 +339,12 @@ function PanelGroupWithForwardedRef({ } = panelDataHelper(groupId, panelDataArray, panelData, prevLayout); if (panelSizePercentage !== collapsedSizePercentage) { - // TODO Store size before collapse + // Store size before collapse; + // This is the size that gets restored if the expand() API is used. + panelSizeBeforeCollapseRef.current.set( + panelData.id, + panelSizePercentage + ); const nextLayout = adjustLayoutByDelta({ delta: collapsedSizePercentage - panelSizePercentage, @@ -398,10 +406,15 @@ function PanelGroupWithForwardedRef({ } = panelDataHelper(groupId, panelDataArray, panelData, prevLayout); if (panelSizePercentage === collapsedSizePercentage) { - // TODO Retrieve size before collapse; this should be the default new size + // Restore this panel to the size it was before it was collapsed, if possible. + const prevPanelSizePercentage = + panelSizeBeforeCollapseRef.current.get(panelData.id); const nextLayout = adjustLayoutByDelta({ - delta: minSizePercentage - panelSizePercentage, + delta: + prevPanelSizePercentage != null + ? prevPanelSizePercentage - panelSizePercentage + : minSizePercentage - panelSizePercentage, groupSizePixels, layout: prevLayout, panelConstraints: panelConstraintsArray, @@ -462,26 +475,6 @@ function PanelGroupWithForwardedRef({ (panelData: PanelData) => { const panelIndex = panelDataArray.indexOf(panelData); - // Before mounting, Panels will not yet have registered themselves. - // This includes server rendering. - // At this point the best we can do is render everything with the same size. - if (panelDataArray.length === 0) { - if (isDevelopment) { - if (!devWarningsRef.current.didLogDefaultSizeWarning) { - if ( - !isBrowser && - panelData.constraints.defaultSizePercentage == null && - panelData.constraints.defaultSizePixels == null - ) { - devWarningsRef.current.didLogDefaultSizeWarning = true; - console.warn( - `WARNING: Panel defaultSizePercentage or defaultSizePixels prop recommended to avoid layout shift after server rendering` - ); - } - } - } - } - return computePanelFlexBoxStyle({ dragState, layout, @@ -644,7 +637,7 @@ function PanelGroupWithForwardedRef({ // External APIs are safe to memoize via committed values ref const resizePanel = useCallback( - (panelData: PanelData, sizePercentage: number) => { + (panelData: PanelData, mixedSizes: Partial) => { const { layout: prevLayout, onLayout, @@ -658,6 +651,11 @@ function PanelGroupWithForwardedRef({ const { groupSizePixels, panelSizePercentage, pivotIndices } = panelDataHelper(groupId, panelDataArray, panelData, prevLayout); + const sizePercentage = getPercentageSizeFromMixedSizes( + mixedSizes, + groupSizePixels + ); + const nextLayout = adjustLayoutByDelta({ delta: sizePercentage - panelSizePercentage, groupSizePixels, diff --git a/packages/react-resizable-panels/src/new/PanelGroupContext.ts b/packages/react-resizable-panels/src/new/PanelGroupContext.ts index 649cda334..a135443bc 100644 --- a/packages/react-resizable-panels/src/new/PanelGroupContext.ts +++ b/packages/react-resizable-panels/src/new/PanelGroupContext.ts @@ -24,7 +24,7 @@ export const PanelGroupContext = createContext<{ isPanelExpanded: (panelData: PanelData) => boolean; registerPanel: (panelData: PanelData) => void; registerResizeHandle: (dragHandleId: string) => ResizeHandler; - resizePanel: (panelData: PanelData, sizePercentage: number) => void; + resizePanel: (panelData: PanelData, mixedSizes: Partial) => void; startDragging: (dragHandleId: string, event: ResizeEvent) => void; stopDragging: () => void; unregisterPanel: (panelData: PanelData) => void; diff --git a/packages/react-resizable-panels/src/new/utils/test-utils.ts b/packages/react-resizable-panels/src/new/utils/test-utils.ts index a54bfe913..1e24ac1f9 100644 --- a/packages/react-resizable-panels/src/new/utils/test-utils.ts +++ b/packages/react-resizable-panels/src/new/utils/test-utils.ts @@ -16,6 +16,49 @@ export function expectToBeCloseToArray( } } +export function mockOffsetWidthAndHeight( + mockWidth = 1_000, + mockHeight = 1_000 +) { + const offsetHeightPropertyDescriptor = Object.getOwnPropertyDescriptor( + HTMLElement.prototype, + "offsetHeight" + ); + + const offsetWidthPropertyDescriptor = Object.getOwnPropertyDescriptor( + HTMLElement.prototype, + "offsetWidth" + ); + + Object.defineProperty(HTMLElement.prototype, "offsetHeight", { + configurable: true, + value: mockHeight, + }); + + Object.defineProperty(HTMLElement.prototype, "offsetWidth", { + configurable: true, + value: mockWidth, + }); + + return function uninstallMocks() { + if (offsetHeightPropertyDescriptor) { + Object.defineProperty( + HTMLElement.prototype, + "offsetHeight", + offsetHeightPropertyDescriptor + ); + } + + if (offsetWidthPropertyDescriptor) { + Object.defineProperty( + HTMLElement.prototype, + "offsetWidth", + offsetWidthPropertyDescriptor + ); + } + }; +} + export function verifyExpectedWarnings( callback: Function, ...expectedMessages: string[] diff --git a/packages/react-resizable-panels/src/vendor/react.ts b/packages/react-resizable-panels/src/vendor/react.ts index 6af4e3d2a..a0f3d73cb 100644 --- a/packages/react-resizable-panels/src/vendor/react.ts +++ b/packages/react-resizable-panels/src/vendor/react.ts @@ -22,6 +22,7 @@ import type { const { createElement, createContext, + createRef, forwardRef, useCallback, useContext, @@ -39,6 +40,7 @@ const useId = (React as any)["useId".toString()] as () => string; export { createElement, createContext, + createRef, forwardRef, useCallback, useContext, From ebe73f1f0b94e6a21d86d020fc4b88bc1f210e31 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 21 Oct 2023 20:14:44 -0400 Subject: [PATCH 14/35] Migrate last remaining docs page to new API --- .../routes/examples/ImperativePanelApi.tsx | 8 ++--- .../src/routes/examples/PixelBasedLayouts.tsx | 31 ++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx b/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx index 355a040f4..b6dc71bdb 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx @@ -120,17 +120,17 @@ function TogglesRow({ panelRef: RefObject; panelSize: number; }) { - const [size, setSize] = useState(20); + const [sizePercentage, setSizePercentage] = useState(20); const onInputChange = (event: ChangeEvent) => { const input = event.currentTarget as HTMLInputElement; - setSize(parseInt(input.value)); + setSizePercentage(parseInt(input.value)); }; const onFormSubmit = (event: FormEvent) => { event.preventDefault(); - panelRef.current?.resize(size); + panelRef.current?.resize({ sizePercentage }); }; return ( @@ -164,7 +164,7 @@ function TogglesRow({ max={100} size={2} type="number" - value={size} + value={sizePercentage} />
diff --git a/packages/react-resizable-panels-website/src/routes/examples/PixelBasedLayouts.tsx b/packages/react-resizable-panels-website/src/routes/examples/PixelBasedLayouts.tsx index ec7d8e243..53a6291e1 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/PixelBasedLayouts.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/PixelBasedLayouts.tsx @@ -1,6 +1,9 @@ -import { Panel, PanelGroup } from "react-resizable-panels"; +import { + new_Panel as Panel, + new_PanelGroup as PanelGroup, +} from "react-resizable-panels"; -import ResizeHandle from "../../components/ResizeHandle"; +import { new_ResizeHandle as ResizeHandle } from "../../components/ResizeHandle"; import { Link } from "react-router-dom"; import AutoSizer from "react-virtualized-auto-sizer"; @@ -38,12 +41,11 @@ export default function PixelBasedLayouts() {
@@ -52,11 +54,11 @@ export default function PixelBasedLayouts() {
- +
middle
- +
right
@@ -79,27 +81,26 @@ export default function PixelBasedLayouts() { - +
left
- +
middle

200px - 300px

-

collapse below 100px

+

collapse below 200px

@@ -155,6 +156,6 @@ const CODE_HOOK_COLLAPSIBLE = ` - +
`; From 2f9afe0ddae4c76a2106c2e3ebc5155b827f6ce4 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 22 Oct 2023 10:01:42 -0400 Subject: [PATCH 15/35] Added more imperative Panel API tests and fixed and edge case --- .../src/new/Panel.test.tsx | 158 +++++++++++++----- .../src/new/PanelGroup.ts | 32 +++- .../src/new/utils/test-utils.ts | 12 ++ 3 files changed, 154 insertions(+), 48 deletions(-) diff --git a/packages/react-resizable-panels/src/new/Panel.test.tsx b/packages/react-resizable-panels/src/new/Panel.test.tsx index 5f8fc3f1c..a6b700dfe 100644 --- a/packages/react-resizable-panels/src/new/Panel.test.tsx +++ b/packages/react-resizable-panels/src/new/Panel.test.tsx @@ -3,7 +3,10 @@ import { act } from "react-dom/test-utils"; import { createRef } from "../vendor/react"; import { ImperativePanelHandle, Panel } from "./Panel"; import { PanelGroup } from "./PanelGroup"; -import { mockOffsetWidthAndHeight } from "./utils/test-utils"; +import { + mockOffsetWidthAndHeight, + verifyExpandedPanelGroupLayout, +} from "./utils/test-utils"; import { MixedSizes } from ".."; describe("PanelGroup", () => { @@ -55,61 +58,132 @@ describe("PanelGroup", () => { }); describe("imperative handle API", () => { - it("should re-expand to the most recent size before collapsing", () => { - const ref = createRef(); - - let mostRecentMixedSizes: MixedSizes | undefined; - - const onResize = (mixedSizes: MixedSizes) => { - mostRecentMixedSizes = mixedSizes; - }; - - act(() => { - root.render( - - - - - ); + describe("collapse and expand", () => { + let leftPanelRef = createRef(); + let rightPanelRef = createRef(); + + let mostRecentLayout: MixedSizes[] | null; + + beforeEach(() => { + leftPanelRef = createRef(); + rightPanelRef = createRef(); + + mostRecentLayout = null; + + const onLayout = (layout: MixedSizes[]) => { + mostRecentLayout = layout; + }; + + act(() => { + root.render( + + + + + ); + }); }); - expect(mostRecentMixedSizes).toEqual({ - sizePercentage: 50, - sizePixels: 500, + it("should expand and collapse the first panel in a group", () => { + verifyExpandedPanelGroupLayout(mostRecentLayout!, [50, 50]); + act(() => { + leftPanelRef.current!.collapse(); + }); + verifyExpandedPanelGroupLayout(mostRecentLayout!, [0, 100]); + act(() => { + leftPanelRef.current!.expand(); + }); + verifyExpandedPanelGroupLayout(mostRecentLayout!, [50, 50]); }); - act(() => { - ref.current!.resize({ sizePercentage: 30 }); + it("should expand and collapse the last panel in a group", () => { + verifyExpandedPanelGroupLayout(mostRecentLayout!, [50, 50]); + act(() => { + rightPanelRef.current!.collapse(); + }); + verifyExpandedPanelGroupLayout(mostRecentLayout!, [100, 0]); + act(() => { + rightPanelRef.current!.expand(); + }); + verifyExpandedPanelGroupLayout(mostRecentLayout!, [50, 50]); }); - expect(mostRecentMixedSizes).toEqual({ - sizePercentage: 30, - sizePixels: 300, + it("should re-expand to the most recent size before collapsing", () => { + verifyExpandedPanelGroupLayout(mostRecentLayout!, [50, 50]); + act(() => { + leftPanelRef.current!.resize({ sizePercentage: 30 }); + }); + verifyExpandedPanelGroupLayout(mostRecentLayout!, [30, 70]); + act(() => { + leftPanelRef.current!.collapse(); + }); + verifyExpandedPanelGroupLayout(mostRecentLayout!, [0, 100]); + act(() => { + leftPanelRef.current!.expand(); + }); + verifyExpandedPanelGroupLayout(mostRecentLayout!, [30, 70]); }); + }); - act(() => { - ref.current!.collapse(); + describe("resize", () => { + let leftPanelRef = createRef(); + let middlePanelRef = createRef(); + let rightPanelRef = createRef(); + + let mostRecentLayout: MixedSizes[] | null; + + beforeEach(() => { + leftPanelRef = createRef(); + middlePanelRef = createRef(); + rightPanelRef = createRef(); + + mostRecentLayout = null; + + const onLayout = (layout: MixedSizes[]) => { + mostRecentLayout = layout; + }; + + act(() => { + root.render( + + + + + + ); + }); }); - expect(mostRecentMixedSizes).toEqual({ - sizePercentage: 5, - sizePixels: 50, + it("should resize the first panel in a group", () => { + verifyExpandedPanelGroupLayout(mostRecentLayout!, [20, 60, 20]); + act(() => { + leftPanelRef.current!.resize({ sizePercentage: 40 }); + }); + verifyExpandedPanelGroupLayout(mostRecentLayout!, [40, 40, 20]); }); - act(() => { - ref.current!.expand(); + it("should resize the middle panel in a group", () => { + verifyExpandedPanelGroupLayout(mostRecentLayout!, [20, 60, 20]); + act(() => { + middlePanelRef.current!.resize({ sizePercentage: 40 }); + }); + verifyExpandedPanelGroupLayout(mostRecentLayout!, [20, 40, 40]); }); - expect(mostRecentMixedSizes).toEqual({ - sizePercentage: 30, - sizePixels: 300, + it("should resize the last panel in a group", () => { + verifyExpandedPanelGroupLayout(mostRecentLayout!, [20, 60, 20]); + act(() => { + rightPanelRef.current!.resize({ sizePercentage: 40 }); + }); + verifyExpandedPanelGroupLayout(mostRecentLayout!, [20, 40, 40]); }); }); }); diff --git a/packages/react-resizable-panels/src/new/PanelGroup.ts b/packages/react-resizable-panels/src/new/PanelGroup.ts index 83b35943e..e2d14ec17 100644 --- a/packages/react-resizable-panels/src/new/PanelGroup.ts +++ b/packages/react-resizable-panels/src/new/PanelGroup.ts @@ -346,8 +346,14 @@ function PanelGroupWithForwardedRef({ panelSizePercentage ); + const isLastPanel = + panelDataArray.indexOf(panelData) === panelDataArray.length - 1; + const delta = isLastPanel + ? panelSizePercentage - collapsedSizePercentage + : collapsedSizePercentage - panelSizePercentage; + const nextLayout = adjustLayoutByDelta({ - delta: collapsedSizePercentage - panelSizePercentage, + delta, groupSizePixels, layout: prevLayout, panelConstraints: panelConstraintsArray, @@ -410,11 +416,19 @@ function PanelGroupWithForwardedRef({ const prevPanelSizePercentage = panelSizeBeforeCollapseRef.current.get(panelData.id); + const baseSizePercentage = + prevPanelSizePercentage != null + ? prevPanelSizePercentage + : minSizePercentage; + + const isLastPanel = + panelDataArray.indexOf(panelData) === panelDataArray.length - 1; + const delta = isLastPanel + ? panelSizePercentage - baseSizePercentage + : baseSizePercentage - panelSizePercentage; + const nextLayout = adjustLayoutByDelta({ - delta: - prevPanelSizePercentage != null - ? prevPanelSizePercentage - panelSizePercentage - : minSizePercentage - panelSizePercentage, + delta, groupSizePixels, layout: prevLayout, panelConstraints: panelConstraintsArray, @@ -656,8 +670,14 @@ function PanelGroupWithForwardedRef({ groupSizePixels ); + const isLastPanel = + panelDataArray.indexOf(panelData) === panelDataArray.length - 1; + const delta = isLastPanel + ? panelSizePercentage - sizePercentage + : sizePercentage - panelSizePercentage; + const nextLayout = adjustLayoutByDelta({ - delta: sizePercentage - panelSizePercentage, + delta, groupSizePixels, layout: prevLayout, panelConstraints: panelConstraintsArray, diff --git a/packages/react-resizable-panels/src/new/utils/test-utils.ts b/packages/react-resizable-panels/src/new/utils/test-utils.ts index 1e24ac1f9..df52bb50b 100644 --- a/packages/react-resizable-panels/src/new/utils/test-utils.ts +++ b/packages/react-resizable-panels/src/new/utils/test-utils.ts @@ -1,3 +1,5 @@ +import { MixedSizes } from "../../../dist/react-resizable-panels.cjs.mjs"; + const util = require("util"); export function expectToBeCloseToArray( @@ -59,6 +61,16 @@ export function mockOffsetWidthAndHeight( }; } +export function verifyExpandedPanelGroupLayout( + actualLayout: MixedSizes[], + expectedPercentages: number[] +) { + expect(actualLayout).toHaveLength(expectedPercentages.length); + expect(actualLayout.map(({ sizePercentage }) => sizePercentage)).toEqual( + expectedPercentages + ); +} + export function verifyExpectedWarnings( callback: Function, ...expectedMessages: string[] From 441b1c7bf0d4cbc0ac97c33a8c33dfcf5308b269 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 22 Oct 2023 11:24:32 -0400 Subject: [PATCH 16/35] Delete old components and outdated spec tests --- README.md | 29 +- .../src/components/ResizeHandle.tsx | 32 +- .../src/routes/EndToEndTesting/index.tsx | 54 +- .../src/routes/examples/Collapsible.tsx | 8 +- .../src/routes/examples/Conditional.tsx | 7 +- .../routes/examples/ExternalPersistence.tsx | 8 +- .../src/routes/examples/Horizontal.tsx | 7 +- .../routes/examples/ImperativePanelApi.tsx | 8 +- .../examples/ImperativePanelGroupApi.tsx | 9 +- .../src/routes/examples/Nested.tsx | 7 +- .../src/routes/examples/Overflow.tsx | 7 +- .../src/routes/examples/Persistence.tsx | 7 +- .../src/routes/examples/PixelBasedLayouts.tsx | 7 +- .../src/routes/examples/Vertical.tsx | 7 +- .../src/routes/examples/types.ts | 14 +- .../src/utils/UrlData.ts | 66 +- .../tests/Collapsing.spec.ts | 18 +- .../tests/CursorStyle.spec.ts | 10 +- .../DevelopmentWarningsAndErrors.spec.ts | 422 ------ .../tests/Group-OnLayout.spec.ts | 26 +- .../tests/ImperativePanelApi.spec.ts | 188 --- .../tests/ImperativePanelGroupApi.spec.ts | 116 -- .../tests/NestedGroups.spec.ts | 14 +- .../tests/Panel-OnCollapse.spec.ts | 31 +- .../tests/Panel-OnResize.spec.ts | 52 +- .../tests/PanelGroup-PixelUnits.spec.ts | 87 +- .../tests/ResizeHandle-OnDragging.spec.ts | 14 +- .../tests/ResizeHandle.spec.ts | 6 +- .../tests/Springy.spec.ts | 22 +- .../tests/Storage.spec.ts | 14 +- .../tests/WindowSplitter.spec.ts | 45 +- .../tests/utils/debug.ts | 15 +- .../tests/utils/panels.ts | 24 +- .../tests/utils/verify.ts | 11 +- packages/react-resizable-panels/CHANGELOG.md | 242 +-- packages/react-resizable-panels/README.md | 104 +- .../src/{new => }/Panel.test.tsx | 13 +- packages/react-resizable-panels/src/Panel.ts | 298 ++-- .../src/PanelContexts.ts | 22 - .../src/{new => }/PanelGroup.test.tsx | 6 +- .../react-resizable-panels/src/PanelGroup.ts | 1295 ++++++++--------- .../src/{new => }/PanelGroupContext.ts | 2 +- .../src/PanelResizeHandle.ts | 17 +- .../src/hooks/useWindowSplitterBehavior.ts | 174 +-- packages/react-resizable-panels/src/index.ts | 45 +- .../react-resizable-panels/src/new/Panel.ts | 266 ---- .../src/new/PanelGroup.ts | 868 ----------- .../src/new/PanelResizeHandle.ts | 193 --- .../react-resizable-panels/src/new/types.ts | 6 - .../src/new/utils/serialization.ts | 70 - packages/react-resizable-panels/src/types.ts | 33 +- .../utils/adjustLayoutByDelta.test.ts | 0 .../{new => }/utils/adjustLayoutByDelta.ts | 0 .../utils/calculateDeltaPercentage.ts | 2 +- .../utils/calculateDragOffsetPercentage.ts | 4 +- .../calculateUnsafeDefaultLayout.test.ts | 0 .../utils/calculateUnsafeDefaultLayout.ts | 0 .../src/{new => }/utils/callPanelCallbacks.ts | 2 +- .../{new => }/utils/compareLayouts.test.ts | 0 .../src/{new => }/utils/compareLayouts.ts | 0 .../utils/computePanelFlexBoxStyle.ts | 2 +- .../computePercentagePanelConstraints.ts | 0 .../utils/convertPercentageToPixels.test.ts | 0 .../utils/convertPercentageToPixels.ts | 0 .../convertPixelConstraintsToPercentages.ts | 0 .../utils/convertPixelsToPercentage.test.ts | 0 .../utils/convertPixelsToPercentage.ts | 0 .../src/utils/coordinates.ts | 149 -- .../{new => }/utils/determinePivotIndices.ts | 2 +- .../calculateAvailablePanelSizeInPixels.ts | 0 .../utils/dom/getAvailableGroupSizePixels.ts | 29 + .../{new => }/utils/dom/getPanelElement.ts | 0 .../utils/dom/getPanelGroupElement.ts | 0 .../utils/dom/getResizeHandleElement.ts | 0 .../utils/dom/getResizeHandleElementIndex.ts | 0 .../utils/dom/getResizeHandleElements,ts | 0 .../dom/getResizeHandleElementsForGroup.ts | 0 .../src/{new => }/utils/events.ts | 0 .../getPercentageSizeFromMixedSizes.test.ts | 0 .../utils/getPercentageSizeFromMixedSizes.ts | 0 .../utils/getResizeEventCursorPosition.ts | 0 .../react-resizable-panels/src/utils/group.ts | 614 -------- .../utils/initializeDefaultStorage.ts | 0 .../utils/numbers/fuzzyCompareNumbers.test.ts | 0 .../utils/numbers/fuzzyCompareNumbers.ts | 2 +- .../utils/numbers/fuzzyNumbersEqual.ts | 0 .../src/{new => }/utils/resizePanel.ts | 1 - .../src/utils/serialization.ts | 13 +- .../src/{new => }/utils/test-utils.ts | 2 +- .../utils/validatePanelConstraints.test.ts | 0 .../utils/validatePanelConstraints.ts | 0 .../utils/validatePanelGroupLayout.test.ts | 0 .../utils/validatePanelGroupLayout.ts | 0 93 files changed, 1432 insertions(+), 4436 deletions(-) delete mode 100644 packages/react-resizable-panels-website/tests/DevelopmentWarningsAndErrors.spec.ts delete mode 100644 packages/react-resizable-panels-website/tests/ImperativePanelApi.spec.ts delete mode 100644 packages/react-resizable-panels-website/tests/ImperativePanelGroupApi.spec.ts rename packages/react-resizable-panels/src/{new => }/Panel.test.tsx (91%) delete mode 100644 packages/react-resizable-panels/src/PanelContexts.ts rename packages/react-resizable-panels/src/{new => }/PanelGroup.test.tsx (97%) rename packages/react-resizable-panels/src/{new => }/PanelGroupContext.ts (94%) delete mode 100644 packages/react-resizable-panels/src/new/Panel.ts delete mode 100644 packages/react-resizable-panels/src/new/PanelGroup.ts delete mode 100644 packages/react-resizable-panels/src/new/PanelResizeHandle.ts delete mode 100644 packages/react-resizable-panels/src/new/types.ts delete mode 100644 packages/react-resizable-panels/src/new/utils/serialization.ts rename packages/react-resizable-panels/src/{new => }/utils/adjustLayoutByDelta.test.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/adjustLayoutByDelta.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/calculateDeltaPercentage.ts (95%) rename packages/react-resizable-panels/src/{new => }/utils/calculateDragOffsetPercentage.ts (86%) rename packages/react-resizable-panels/src/{new => }/utils/calculateUnsafeDefaultLayout.test.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/calculateUnsafeDefaultLayout.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/callPanelCallbacks.ts (95%) rename packages/react-resizable-panels/src/{new => }/utils/compareLayouts.test.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/compareLayouts.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/computePanelFlexBoxStyle.ts (95%) rename packages/react-resizable-panels/src/{new => }/utils/computePercentagePanelConstraints.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/convertPercentageToPixels.test.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/convertPercentageToPixels.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/convertPixelConstraintsToPercentages.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/convertPixelsToPercentage.test.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/convertPixelsToPercentage.ts (100%) delete mode 100644 packages/react-resizable-panels/src/utils/coordinates.ts rename packages/react-resizable-panels/src/{new => }/utils/determinePivotIndices.ts (74%) rename packages/react-resizable-panels/src/{new => }/utils/dom/calculateAvailablePanelSizeInPixels.ts (100%) create mode 100644 packages/react-resizable-panels/src/utils/dom/getAvailableGroupSizePixels.ts rename packages/react-resizable-panels/src/{new => }/utils/dom/getPanelElement.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/dom/getPanelGroupElement.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/dom/getResizeHandleElement.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/dom/getResizeHandleElementIndex.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/dom/getResizeHandleElements,ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/dom/getResizeHandleElementsForGroup.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/events.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/getPercentageSizeFromMixedSizes.test.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/getPercentageSizeFromMixedSizes.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/getResizeEventCursorPosition.ts (100%) delete mode 100644 packages/react-resizable-panels/src/utils/group.ts rename packages/react-resizable-panels/src/{new => }/utils/initializeDefaultStorage.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/numbers/fuzzyCompareNumbers.test.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/numbers/fuzzyCompareNumbers.ts (88%) rename packages/react-resizable-panels/src/{new => }/utils/numbers/fuzzyNumbersEqual.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/resizePanel.ts (95%) rename packages/react-resizable-panels/src/{new => }/utils/test-utils.ts (97%) rename packages/react-resizable-panels/src/{new => }/utils/validatePanelConstraints.test.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/validatePanelConstraints.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/validatePanelGroupLayout.test.ts (100%) rename packages/react-resizable-panels/src/{new => }/utils/validatePanelGroupLayout.ts (100%) diff --git a/README.md b/README.md index 80da3140d..5fb392754 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ React Resizable Panels logo ## react-resizable-panels + React components for resizable panel groups/layouts. -* [View the website](https://react-resizable-panels.vercel.app/) -* [Try it on CodeSandbox](https://codesandbox.io/s/react-resizable-panels-zf7hwd) -* [Read the documentation](https://github.com/bvaughn/react-resizable-panels/tree/main/packages/react-resizable-panels) -* [View the changelog](https://github.com/bvaughn/react-resizable-panels/blob/main/packages/react-resizable-panels/CHANGELOG.md) +- [View the website](https://react-resizable-panels.vercel.app/) +- [Try it on CodeSandbox](https://codesandbox.io/s/react-resizable-panels-zf7hwd) +- [Read the documentation](https://github.com/bvaughn/react-resizable-panels/tree/main/packages/react-resizable-panels) +- [View the changelog](https://github.com/bvaughn/react-resizable-panels/blob/main/packages/react-resizable-panels/CHANGELOG.md) Supported input methods include mouse, touch, and keyboard (via [Window Splitter](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/)). @@ -26,13 +27,13 @@ The `Panel` API doesn't _require_ `id` and `order` props because they aren't nec {renderSideBar && ( <> - + )} - +
@@ -43,6 +44,7 @@ The `Panel` API doesn't _require_ `id` and `order` props because they aren't nec By default, this library uses `localStorage` to persist layouts. With server rendering, this can cause a flicker when the default layout (rendered on the server) is replaced with the persisted layout (in `localStorage`). The way to avoid this flicker is to also persist the layout with a cookie like so: ##### Server component + ```tsx import ResizablePanels from "@/app/ResizablePanels"; import { cookies } from "next/headers"; @@ -60,15 +62,16 @@ export function ServerComponent() { ``` ##### Client component + ```tsx "use client"; import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; export function ClientComponent({ - defaultLayout = [33, 67] + defaultLayout = [33, 67], }: { - defaultLayout: number[] | undefined + defaultLayout: number[] | undefined; }) { const onLayout = (sizes: number[]) => { document.cookie = `react-resizable-panels:layout=${JSON.stringify(sizes)}`; @@ -76,16 +79,12 @@ export function ClientComponent({ return ( - - {/* ... */} - + {/* ... */} - - {/* ... */} - + {/* ... */} ); } ``` -A demo of this is available [here](https://github.com/bvaughn/react-resizable-panels-demo-ssr). \ No newline at end of file +A demo of this is available [here](https://github.com/bvaughn/react-resizable-panels-demo-ssr). diff --git a/packages/react-resizable-panels-website/src/components/ResizeHandle.tsx b/packages/react-resizable-panels-website/src/components/ResizeHandle.tsx index 43a1c93cc..928ed0306 100644 --- a/packages/react-resizable-panels-website/src/components/ResizeHandle.tsx +++ b/packages/react-resizable-panels-website/src/components/ResizeHandle.tsx @@ -1,12 +1,9 @@ -import { - PanelResizeHandle, - new_PanelResizeHandle as NewPanelResizeHandle, -} from "react-resizable-panels"; +import { PanelResizeHandle } from "react-resizable-panels"; import Icon from "./Icon"; import styles from "./ResizeHandle.module.css"; -export default function ResizeHandle({ +export function ResizeHandle({ className = "", collapsed = false, id, @@ -30,28 +27,3 @@ export default function ResizeHandle({ ); } - -export function new_ResizeHandle({ - className = "", - collapsed = false, - id, -}: { - className?: string; - collapsed?: boolean; - id?: string; -}) { - return ( - -
- - -
-
- ); -} diff --git a/packages/react-resizable-panels-website/src/routes/EndToEndTesting/index.tsx b/packages/react-resizable-panels-website/src/routes/EndToEndTesting/index.tsx index 75015aaa0..4794fb60a 100644 --- a/packages/react-resizable-panels-website/src/routes/EndToEndTesting/index.tsx +++ b/packages/react-resizable-panels-website/src/routes/EndToEndTesting/index.tsx @@ -9,8 +9,6 @@ import { import { ImperativePanelGroupHandle, ImperativePanelHandle, - Units, - getAvailableGroupSizePixels, } from "react-resizable-panels"; import { urlPanelGroupToPanelGroup, urlToUrlData } from "../../utils/UrlData"; @@ -55,8 +53,10 @@ function EndToEndTesting() { const [panelIds, setPanelIds] = useState([]); const [panelGroupId, setPanelGroupId] = useState(""); const [panelGroupIds, setPanelGroupIds] = useState([]); - const [size, setSize] = useState(0); - const [units, setUnits] = useState(""); + const [sizePercentage, setSizePercentage] = useState( + undefined + ); + const [sizePixels, setSizePixels] = useState(undefined); const [layoutString, setLayoutString] = useState(""); const debugLogRef = useRef(null); @@ -80,10 +80,6 @@ function EndToEndTesting() { ); setPanelGroupIds(panelGroupIds); setPanelGroupId(panelGroupIds[0]); - - // const panelGroupElement = document.querySelector("[data-panel-group]")!; - // const units = panelGroupElement.getAttribute("data-panel-group-units")!; - // setUnits(units); }; window.addEventListener("popstate", (event) => { @@ -113,12 +109,11 @@ function EndToEndTesting() { panelId ) as ImperativePanelHandle; if (panel != null) { - const percentage = panel.getSize("percentages"); - const pixels = panel.getSize("pixels"); + const { sizePercentage, sizePixels } = panel.getSize(); - panelElement.textContent = `${percentage.toFixed( + panelElement.textContent = `${sizePercentage.toFixed( 1 - )}%\n${pixels.toFixed(1)}px`; + )}%\n${sizePixels.toFixed(1)}px`; } } }, 0); @@ -174,12 +169,16 @@ function EndToEndTesting() { const onSizeInputChange = (event: ChangeEvent) => { const value = event.currentTarget.value; - setSize(parseFloat(value)); - }; - const onUnitsSelectChange = (event: ChangeEvent) => { - const value = event.currentTarget.value; - setUnits(value); + if (value.endsWith("%")) { + setSizePercentage(parseFloat(value)); + setSizePixels(undefined); + } else if (value.endsWith("px")) { + setSizePercentage(undefined); + setSizePixels(parseFloat(value)); + } else { + throw Error(`Invalid size: ${value}`); + } }; const onCollapseButtonClick = () => { @@ -202,7 +201,7 @@ function EndToEndTesting() { const idToRefMap = idToRefMapRef.current; const panel = idToRefMap.get(panelId); if (panel && assertImperativePanelHandle(panel)) { - panel.resize(size, (units as any) || undefined); + panel.resize({ sizePercentage, sizePixels }); } }; @@ -210,10 +209,7 @@ function EndToEndTesting() { const idToRefMap = idToRefMapRef.current; const panelGroup = idToRefMap.get(panelGroupId); if (panelGroup && assertImperativePanelGroupHandle(panelGroup)) { - panelGroup.setLayout( - JSON.parse(layoutString), - (units as any) || undefined - ); + panelGroup.setLayout(JSON.parse(layoutString)); } }; @@ -251,7 +247,7 @@ function EndToEndTesting() { className={styles.Input} id="sizeInput" onChange={onSizeInputChange} - placeholder="Size" + placeholder="Size (% or px)" type="number" />
- -